Skip to content

Commit 9aeffae

Browse files
committed
Use SecureRandom.alphanumeric for SecureRandom.base36
Ruby 3.3 allows passing a list of characters to `SecureRandom.alphanumeric`. For `SecureRandom.base36` using `choose` is faster than the current implementation. For `SecureRandom.base58` it is a bit slower. I've also added a test to make sure passing nil as the length defaults the length to 16. _Benchmark__ ```ruby require "bundler/inline" gemfile(true) do source "https://rubygems.org" git_source(:github) { |repo| "https://github.com/#{repo}.git" } gem "rails", github: "rails/rails", branch: "main" gem "benchmark-ips" end require "active_support" require "active_support/core_ext/securerandom" module SecureRandom def self.fast_base36(n) alphanumeric(n, chars: BASE36_ALPHABET) end end [10, 100, 1000, 10000].each do |length| puts puts " #{length} ".center(80, "=") puts Benchmark.ips do |x| x.report("base36") { SecureRandom.base36(length) } x.report("fast_base36") { SecureRandom.fast_base36(length) } x.compare! end end ``` ``` ====================================== 10 ====================================== Warming up -------------------------------------- base36 20.513k i/100ms fast_base36 24.843k i/100ms Calculating ------------------------------------- base36 200.940k (±13.8%) i/s - 984.624k in 5.060203s fast_base36 235.531k (± 5.7%) i/s - 1.192M in 5.080574s Comparison: fast_base36: 235530.9 i/s base36: 200939.9 i/s - same-ish: difference falls within error ===================================== 100 ====================================== Warming up -------------------------------------- base36 2.746k i/100ms fast_base36 2.995k i/100ms Calculating ------------------------------------- base36 25.559k (± 8.5%) i/s - 129.062k in 5.087961s fast_base36 30.265k (± 6.6%) i/s - 152.745k in 5.070263s Comparison: fast_base36: 30264.7 i/s base36: 25558.8 i/s - 1.18x slower ===================================== 1000 ===================================== Warming up -------------------------------------- base36 278.000 i/100ms fast_base36 308.000 i/100ms Calculating ------------------------------------- base36 2.595k (±11.6%) i/s - 12.788k in 5.007921s fast_base36 3.133k (± 6.1%) i/s - 15.708k in 5.033310s Comparison: fast_base36: 3132.6 i/s base36: 2594.9 i/s - 1.21x slower ==================================== 10000 ===================================== Warming up -------------------------------------- base36 24.000 i/100ms fast_base36 34.000 i/100ms Calculating ------------------------------------- base36 256.601 (± 8.6%) i/s - 1.296k in 5.089604s fast_base36 322.119 (± 6.5%) i/s - 1.632k in 5.089614s Comparison: fast_base36: 322.1 i/s base36: 256.6 i/s - 1.26x slower ```
1 parent 4a9974a commit 9aeffae

File tree

2 files changed

+46
-12
lines changed

2 files changed

+46
-12
lines changed

activesupport/lib/active_support/core_ext/securerandom.rb

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,18 @@ module SecureRandom
1616
#
1717
# p SecureRandom.base58 # => "4kUgL2pdQMSCQtjE"
1818
# p SecureRandom.base58(24) # => "77TMHrHJFvFDwodq8w7Ev2m7"
19-
def self.base58(n = 16)
20-
SecureRandom.random_bytes(n).unpack("C*").map do |byte|
21-
idx = byte % 64
22-
idx = SecureRandom.random_number(58) if idx >= 58
23-
BASE58_ALPHABET[idx]
24-
end.join
19+
if RUBY_VERSION >= "3.3"
20+
def self.base58(n = 16)
21+
SecureRandom.alphanumeric(n, chars: BASE58_ALPHABET)
22+
end
23+
else
24+
def self.base58(n = 16)
25+
SecureRandom.random_bytes(n).unpack("C*").map do |byte|
26+
idx = byte % 64
27+
idx = SecureRandom.random_number(58) if idx >= 58
28+
BASE58_ALPHABET[idx]
29+
end.join
30+
end
2531
end
2632

2733
# SecureRandom.base36 generates a random base36 string in lowercase.
@@ -35,11 +41,17 @@ def self.base58(n = 16)
3541
#
3642
# p SecureRandom.base36 # => "4kugl2pdqmscqtje"
3743
# p SecureRandom.base36(24) # => "77tmhrhjfvfdwodq8w7ev2m7"
38-
def self.base36(n = 16)
39-
SecureRandom.random_bytes(n).unpack("C*").map do |byte|
40-
idx = byte % 64
41-
idx = SecureRandom.random_number(36) if idx >= 36
42-
BASE36_ALPHABET[idx]
43-
end.join
44+
if RUBY_VERSION >= "3.3"
45+
def self.base36(n = 16)
46+
SecureRandom.alphanumeric(n, chars: BASE36_ALPHABET)
47+
end
48+
else
49+
def self.base36(n = 16)
50+
SecureRandom.random_bytes(n).unpack("C*").map do |byte|
51+
idx = byte % 64
52+
idx = SecureRandom.random_number(36) if idx >= 36
53+
BASE36_ALPHABET[idx]
54+
end.join
55+
end
4456
end
4557
end

activesupport/test/core_ext/secure_random_test.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,18 @@ def test_base58_with_length
2828
assert_match(/^[^0OIl]+$/, s2)
2929
end
3030

31+
def test_base58_with_nil
32+
s1 = SecureRandom.base58(nil)
33+
s2 = SecureRandom.base58(nil)
34+
35+
assert_not_equal s1, s2
36+
assert_equal 16, s1.length
37+
assert_match(/^[a-zA-Z0-9]+$/, s1)
38+
assert_match(/^[a-zA-Z0-9]+$/, s2)
39+
assert_match(/^[^0OIl]+$/, s1)
40+
assert_match(/^[^0OIl]+$/, s2)
41+
end
42+
3143
def test_base36
3244
s1 = SecureRandom.base36
3345
s2 = SecureRandom.base36
@@ -47,4 +59,14 @@ def test_base36_with_length
4759
assert_match(/^[a-z0-9]+$/, s1)
4860
assert_match(/^[a-z0-9]+$/, s2)
4961
end
62+
63+
def test_base36_with_nil
64+
s1 = SecureRandom.base36(nil)
65+
s2 = SecureRandom.base36(nil)
66+
67+
assert_not_equal s1, s2
68+
assert_equal 16, s1.length
69+
assert_match(/^[a-z0-9]+$/, s1)
70+
assert_match(/^[a-z0-9]+$/, s2)
71+
end
5072
end

0 commit comments

Comments
 (0)