[erlang-questions] On OTP rand module difference between OTP 19 and OTP 20

Raimo Niskanen raimo+erlang-questions@REDACTED
Mon Sep 4 10:48:15 CEST 2017


On Sat, Sep 02, 2017 at 04:35:39PM -0700, Michael Truog wrote:
> On 09/01/2017 05:13 AM, Raimo Niskanen wrote:
> > On Fri, Sep 01, 2017 at 04:00:59AM -0700, Michael Truog wrote:
> >> On 09/01/2017 01:54 AM, Raimo Niskanen wrote:
> >>> On Thu, Aug 31, 2017 at 10:29:34PM -0700, Michael Truog wrote:
> >>> :
> >>>> I have some examples that can make this desire a bit clearer:
> >>>>
> >>>> https://github.com/CloudI/cloudi_core/blob/a1c10a02245f0f4284d701a2ee5f07aad17f6e51/src/cloudi_core_i_runtime_testing.erl#L139-L149
> >>>>
> >>>>        % use Box-Muller transformation to generate Gaussian noise
> >>>>        % (G. E. P. Box and Mervin E. Muller,
> >>>>        %  A Note on the Generation of Random Normal Deviates,
> >>>>        %  The Annals of Mathematical Statistics (1958),
> >>>>        %  Vol. 29, No. 2 pp. 610–611)
> >>>>        X1 = random(),
> >>>>        X2 = PI2 * random(),
> >>>>        K = StdDev * math:sqrt(-2.0 * math:log(X1)),
> >>>>        Result1 = erlang:max(erlang:round(Mean + K * math:cos(X2)), 1),
> >>>>        Result2 = erlang:max(erlang:round(Mean + K * math:sin(X2)), 1),
> >>>>        sleep(Result2),
> >>> Why not use rand:normal/3?
> >>>
> >>> It uses the Ziggurat Method and is supposed to be much faster and
> >>> numerically more stable than the basic Box-Muller method.
> >>>
> >> The Box-Muller is simpler and producing 2 results instead of 1 .  I believe I looked at the source code for rand:normal/3 and expected the Box-Muller to be faster only because it creates 2 results, though I should check that.  I will have to investigate it more.
> > Simpler - yes.
> >
> > The basic benchmark in rand_SUITE indicates that rand:normal() is only
> > about 50% slower than rand:uniform(1 bsl 58) (internal word size),
> > which I think is a very good number.
> >
> > The Box-Muller transform method needs 4 calls to the 'math' module for
> > non-trivial floating point functions i.e log(), sqrt(), cos() and sin(),
> > which is why I think that "must" be slower.
> >
> > But I have also not measured... :-/
> >
> > Looking forward to hear your results!
> I have some interesting results.
> 
> These results use https://github.com/okeuday/erlbench which includes a copy of the source code at https://github.com/okeuday/quickrand :
> 
> TEST pseudo_randomness
> N == 10000 (10 runs)
>           18_bxor_abs get:     1612.7 us (  1.3)
> 18_erlang:system_tim get:     1254.1 us (  1.0)
>          18_monotonic get:     1372.5 us (  1.1)
>   18_os:system_time/1 get:     1221.7 us (  1.0)
> 19_os:perf_counter/1 get:     3752.2 us (  3.1)
>        20_rand:normal get:     6832.0 us (  5.6)
>         20_rand_exrop get:     3949.3 us (  3.2)
>      20_rand_exs1024s get:    12073.3 us (  9.9)
>          20_rand_exsp get:     3390.4 us (  2.8)
>        os:timestamp/0 get:     1392.3 us (  1.1)
> os_time:perf_counter get:     4109.4 us (  3.4)
> quickrand_c:floatR/0 get:     5776.0 us (  4.7)
> quickrand_c:floatR/1 get:     5704.3 us (  4.7)
>     quickrand_c:uni/1 get:     4015.2 us (  3.3)
>     quickrand_c:uni/2 get:     3960.7 us (  3.2)
> quickrand_c_normal/2 get:     9329.5 us (  7.6)
> quickrand_c_normal/3 get:     8917.7 us (  7.3)
> random_wh06_int:unif get:    10777.5 us (  8.8)
> random_wh82:uniform/ get:     4750.0 us (  3.9)
> random_wh82_int:unif get:     4866.4 us (  4.0)
> 
> The function names that are relevant for a normal distribution are:
>        20_rand:normal ->   rand:normal/0 (when using rand:seed(exsp, _))
>          20_rand_exsp ->   rand:uniform/1 (when using rand:seed(exsp, _))
> quickrand_c:floatR/0 ->   quickrand_cache:floatR/0
> quickrand_c:floatR/1 ->   quickrand_cache:floatR/1
> quickrand_c_normal/2 ->   quickrand_cache_normal:box_muller/2
> quickrand_c_normal/3 -> quickrand_cache_normal:box_muller/3
> 
> The rand module exsp algorithm was used here because it is the fastest pseudo-random number generator in the rand module.

Thank you for sharing these numbers!

> 
> A rough look at the latency associated with the normal distribution method, ignoring the latency for random number source is:
> rand:normal/0
>    3441.6 us = 6832.0 us - (rand:uniform/1 3390.4 us)

Should not the base value come from rand:uniform/0 instead.  I know the
difference is not big - rand_SUITE:measure/1 suggests 3%, but it also
suggests that rand:normal/0 is about 50% slower than rand:uniform/0 while
your numbers suggests 100% slower.  Slightly strange...

> quickrand_cache_normal:box_muller/2
>    3553.5 us = 9329.5 us - (quickrand_cache:floatR/0 5776.0 us)
> quickrand_cache_normal:box_muller/3
>    3213.4 us = 8917.7 us - (quickrand_cache:floatR/1 5704.3us)

It is really interesting to see that the calls to the 'math' module
does not slow that algorithm down very much (hardly noticable)!

> 
> So, this helps to show that the latency with both methods is very similar if you ignore the random number generation.  However, it likely requires some explanation:  The quickrand_cache module is what I am using here for random number generation, which stores cached data from crypto:strong_rand_bytes/1 with a default size of 64KB for the cache.  The difference between the functions quickrand_cache_normal:box_muller/2 and quickrand_cache_normal:box_muller/3 is that the first uses the process dictionary while the second uses a state variable.  Using the large amount of cached random data, the latency associated with individual calls to crypto:strong_rand_bytes/1 is avoided at the cost of the extra memory consumption, and the use of the cache makes the speed of random number generation similar to the speed of pseudo-random number generation that occurs in the rand module.

We should add a 'rand' plugin to the 'crypto' module that does this
buffered crypto:strong_random_bytes/1 trick.  There is something like that
in rand_SUITE, but we should really have an official one.

I also wonder where the sweet spot is?  64 KB seems like a lot of buffer.

> 
> In CloudI, I instead use quickrand_normal:box_muller/2 to avoid the use of cached data to keep the memory use minimal (the use-case there doesn't require avoiding the latency associated with crypto:strong_rand_bytes/1 because it is adding latency for testing (at https://github.com/CloudI/cloudi_core/blob/299df02e6d22103415c8ba14379e90ca8c3d3b82/src/cloudi_core_i_runtime_testing.erl#L138) and it is best using a cryptographic random source to keep the functionality widely applicable).  However, the same function calls occur in the quickrand Box-Muller transformation source code, so the overhead is the same.
> 
> I used Erlang/OTP 20.0 (without HiPE) using the hardware below:
> |Core i7 2670QM 2.2GHz 1 cpu, 4 cores/cpu, 2 hts/core
> L2:4×256KB L3:6MB RAM:8GB:DDR3-1333MHz
> Sandy Bridge-HE-4 (Socket G2)
> 
> Best Regards,
> Michael
> |

Best regards
-- 

/ Raimo Niskanen, Erlang/OTP, Ericsson AB



More information about the erlang-questions mailing list