* The cache will return a sampler equivalent to * {@link org.apache.commons.rng.sampling.distribution#PoissonSampler(UniformRandomProvider, double)}. *
* The cache allows the {@link PoissonSampler} construction cost to be minimised * for low size Poisson samples. The cache is advantageous under the following * conditions: *
* If the sample size to be made with the same sampler is large * then the construction cost is minimal compared to the sampling time. *
* Performance improvement is dependent on the speed of the * {@link UniformRandomProvider}. A fast provider can obtain a two-fold speed * improvement for a single-use Poisson sampler. *
* The cache is thread safe.
*/
public class PoissonSamplerCache {
/**
* The minimum N covered by the cache where
* {@code N = (int)Math.floor(mean)}.
*/
private final int minN;
/**
* The maximum N covered by the cache where
* {@code N = (int)Math.floor(mean)}.
*/
private final int maxN;
/** The cache of states between {@link minN} and {@link maxN}. */
private final AtomicReferenceArray
* A value of {@code mean} outside the range of the cache is valid. * * @param rng Generator of uniformly distributed random numbers. * @param mean Mean. * @return A Poisson sampler * @throws IllegalArgumentException if {@code mean <= 0}. */ public DiscreteSampler getPoissonSampler(UniformRandomProvider rng, double mean) { // Ensure the same functionality as the PoissonSampler by // using a SmallMeanPoissonSampler under the switch point. if (mean < WrapperPoissonSampler.PIVOT) return new SmallMeanPoissonSampler(rng, mean); // Convert the mean into an integer. final int n = (int) Math.floor(mean); if (n > maxN) // Outside the range of the cache. return new LargeMeanPoissonSampler(rng, mean); // Look in the cache for a state that can be reused. // Note: The cache is offset by minN. final int index = n - minN; LargeMeanPoissonSamplerState state = values.get(index); if (state == null) { // Compute and store for reuse state = LargeMeanPoissonSamplerState.create(n); // Only set this once as it will be the same. // Allows concurrent threads to compute the state without // excess synchronisation once the state is stored. values.compareAndSet(index, null, state); } // Compute the remaining fraction of the mean final double lambdaFractional = mean - n; return new LargeMeanPoissonSampler(rng, state, lambdaFractional); } } {code} I've left out the implementation of the {{LargeMeanPoissonSamplerState}}. Suffice to say that the {{LargeMeanPoissonSampler}} computes it and it is a nested class. This maintains all the functionality of the {{LargeMeanPoissonSampler}} in one place. Q. Should I pursue this addition? > PoissonSampler single use speed improvements > -------------------------------------------- > > Key: RNG-50 > URL: https://issues.apache.org/jira/browse/RNG-50 > Project: Commons RNG > Issue Type: Improvement > Affects Versions: 1.0 > Reporter: Alex D Herbert > Priority: Minor > Attachments: PoissonSamplerTest.java, jmh-result.csv > > > The Sampler architecture of {{org.apache.commons.rng.sampling.distribution}} is nicely written for fast sampling of small dataset sizes. The constructors for the samplers do not check the input parameters are valid for the respective distributions (in contrast to the old {{org.apache.commons.math3.random.distribution}} classes). I assume this is a design choice for speed. Thus most of the samplers can be used within a loop to sample just one value with very little overhead. > The {{PoissonSampler}} precomputes log factorial numbers upon construction if the mean is above 40. This is done using the {{InternalUtils.FactorialLog}} class. As of version 1.0 this internal class is currently only used in the {{PoissonSampler}}. > The cache size is limited to 2*PIVOT (where PIVOT=40). But it creates and precomputes the cache every time a PoissonSampler is constructed if the mean is above the PIVOT value. > Why not create this once in a static block for the PoissonSampler? > {code:java} > /** {@code log(n!)}. */ > private static final FactorialLog factorialLog; > > static > { > factorialLog = FactorialLog.create().withCache((int) (2 * PoissonSampler.PIVOT)); > } > {code} > This will make the construction cost of a new {{PoissonSampler}} negligible. If the table is computed dynamically as a static construction method then the overhead will be in the first use. Thus the following call will be much faster: > {code:java} > UniformRandomProvider rng = ...; > int value = new PoissonSampler(rng, 50).sample(); > {code} > I have tested this modification (see attached file) and the results are: > {noformat} > Mean 40 Single construction ( 7330792) vs Loop construction (24334724) (3.319522.2x faster) > Mean 40 Single construction ( 7330792) vs Loop construction with static FactorialLog ( 7990656) (1.090013.2x faster) > Mean 50 Single construction ( 6390303) vs Loop construction (19389026) (3.034132.2x faster) > Mean 50 Single construction ( 6390303) vs Loop construction with static FactorialLog ( 6146556) (0.961857.2x faster) > Mean 60 Single construction ( 6041165) vs Loop construction (21337678) (3.532047.2x faster) > Mean 60 Single construction ( 6041165) vs Loop construction with static FactorialLog ( 5329129) (0.882136.2x faster) > Mean 70 Single construction ( 6064003) vs Loop construction (23963516) (3.951765.2x faster) > Mean 70 Single construction ( 6064003) vs Loop construction with static FactorialLog ( 5306081) (0.875013.2x faster) > Mean 80 Single construction ( 6064772) vs Loop construction (26381365) (4.349935.2x faster) > Mean 80 Single construction ( 6064772) vs Loop construction with static FactorialLog ( 6341274) (1.045591.2x faster) > {noformat} > Thus the speed improvements would be approximately 3-4 fold for single use Poisson sampling. -- This message was sent by Atlassian JIRA (v7.6.3#76005)