Why the performance is so vastly different here? (Random::Secure)

Following is a base58 generator, i generate one million times of base58 string.

version 1:

# base58.cr

module Base58
  extend self
  
  ALPHABET = "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ".chars

  def random(chars : Int32)
    String.build(chars) do |sb|
      chars.times { sb << ALPHABET.sample }
    end
  end
end

Time result: (Fast)

real    0m1.097s
user    0m1.157s
sys     0m0.083s

Version 2: (Almost 40x times slow)

module Base58
  extend self
  
  ALPHABET = "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ".chars

  def random(chars : Int32)
    String.build(chars) do |sb|
      chars.times { sb << ALPHABET[Random::Secure.rand(57)] }
    end
  end
end
real    0m42.573s
user    0m18.023s
sys     0m24.657s

Following is common code for generate one million times base58 string from my laptop.

require "./src/base58"

1000000.times { Base58.random(58) }

I try do profile use hotspot, i find the 75% time consume on rand int for version 2(slow vesion), if i am not misunderstood somethings


Following is version 1(quick use Enumerable#sample version)


EDIT:

Okay, i guess i know why, the quick sample version is only generate Random::Default once, and then run #next_u to get next random number, so it more quickly.

So, there is another question, is the quick version is cryptographically secure?

Thank you.

You basically found the answer yourself :wink:
Array#sample uses Random::DEFAULT which is a a fast PRNG that is not cryptographically secure.
You can read more on this on Random - Crystal 1.5.0