Search code examples
rubyperformanceruby-2.4

How expensive is generating a random number in Ruby?


Say you want to generate a random number between 1 and 1 billion:

rand(1..1_000_000_000)

Will Ruby create an array from that range every time you call this line of code?

Rubocop suggests this approach over rand(1_000_000_000)+1 but it seems there's potential for pain.

Ruby's docs say this:

# When +max+ is a Range, +rand+ returns a random number where
# range.member?(number) == true.

Where +max+ is the argument passed to rand, but it doesn't say how it gets the number argument. I'm also not sure if calling .member? on a range is performant.

Any ideas?

I can use benchmark but still curious about the inner workings here.


Solution

  • No, Ruby will not create an array from that range, unless you explicitly call the .to_a method on the Range object. In fact, rand() doesn't work on arrays - .sample is the method to use for returning a random element from an array.

    The Range class includes Enumerable so you get Enumerable's iteration methods without having to convert the range into an array. The lower and upper limits for a Range are (-Float::INFINITY..Float::INFINITY), although that will result in a Numerical argument out of domain error if you pass it into rand.

    As for .member?, that method simply calls a C function called range_cover that calls another one called r_cover_p which checks if a value is between two numbers or strings.

    To test the difference in speed between passing a range to rand and calling sample on an array, you can perform the following test:

    require 'benchmark'
    
    puts Benchmark.measure { rand(0..10_000_000) }
    => 0.000000   0.000000   0.000000 (  0.000009)
    
    puts Benchmark.measure { (0..10_000_000).to_a.sample }
    => 0.300000   0.030000   0.330000 (  0.347752)
    

    As you can see in the first example, passing in a range as a parameter to rand is extremely rapid.

    Contrarily, calling .to_a.sample on a range is rather slow. This is due to the array creation process which requires allocating the appropriate data into memory. The .sample method should be relatively fast as it simply passes a random and unique index into the array and returns that element.

    To check out the code for range have a look here.