[
https://issues.apache.org/jira/browse/RNG-50?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=16560761#comment-16560761
]
Alex D Herbert edited comment on RNG-50 at 7/28/18 2:25 PM:
------------------------------------------------------------
{quote}I've missed something: If the factorials are precomputed with a cache of
80, you are still going to recompute a lot for 0 < mean < 65535.
{quote}
For my initial use case of creating a single {{PoissonSampler}} and then
sampling only once it will make a difference.
The question about the size of the cache requires some investigation on what
values of {{log(n!)}} are requested during sampling. I'll mock up a test that
samples from mean=40 onwards and get the statistics on how {{log(n!)}} is used.
There may be a sensible cache size. Or it may have to be varied depending on
the mean. If the range is limited then the cache could be modified to only
support a small range. There may also be a use case for passing an existing
cache to the constructor.
The method is only called at one place inside the algorithm loop. The first
call in this section of code:
{code:java}
final double lambda = Math.floor(meanPoisson);
final double lambdaFractional = meanPoisson - lambda;
final double logLambda = Math.log(lambda);
final double logLambdaFactorial = factorialLog((int) lambda);
{code}
Is actually constant (since {{meanPoisson}} is fixed) and these values can be
stored in the constructor. It will be interesting to see if the cache is needed
at all.
was (Author: alexherbert):
{quote}I've missed something: If the factorials are precomputed with a cache of
80, you are still going to recompute a lot for 0 < mean < 65535.
{quote}
For my initial use case of creating a single {{PoissonSampler}} and then
sampling only once it will make a difference.
The question about the size of the cache requires some investigation on what
values of {{log(n!)}} are requested during sampling. I'll mock up a test that
samples from mean=40 onwards and get the statistics on how {{log(n!)}} log is
used. There may be a sensible cache size. Or it may have to be varied depending
on the mean. If the range is limited then the cache could be modified to only
support a small range. There may also be a use case for passing an existing
cache to the constructor.
The method is only called at one place inside the algorithm loop. The first
call in this section of code:
{code:java}
final double lambda = Math.floor(meanPoisson);
final double lambdaFractional = meanPoisson - lambda;
final double logLambda = Math.log(lambda);
final double logLambdaFactorial = factorialLog((int) lambda);
{code}
Is actually constant (since {{meanPoisson}} is fixed) and these values can be
stored in the constructor. It will be interesting to see if the cache is needed
at all.
> 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
>
>
> 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)