From 71318b4cef0c6a7cf09109674e7bd95c6d931471 Mon Sep 17 00:00:00 2001 From: Connor Shea <2977353+connorshea@users.noreply.github.com> Date: Thu, 2 Jul 2026 15:44:36 -0600 Subject: [PATCH] Make Base.numerify single-pass and allocation-free per digit Replace the sub-then-gsub double scan with a single gsub whose block tracks whether the leading (non-zero) digit has been emitted, and look digits up in a frozen DIGITS table instead of calling Integer#to_s for every replaced character. Benchmark (Ruby 3.4.9, arm64-darwin25, benchmark-ips): require 'benchmark/ips' require 'faker' Benchmark.ips do |x| x.config(warmup: 1, time: 2) x.report("numerify('###-###-####')") { Faker::Base.numerify('###-###-####') } x.report('PhoneNumber.phone_number') { Faker::PhoneNumber.phone_number } end Results: main: numerify('###-###-####') 486.140k (+/- 4.8%) i/s PhoneNumber.phone_number 100.815k (+/-15.3%) i/s this commit: numerify('###-###-####') 601.743k (+/- 5.2%) i/s (~1.24x) PhoneNumber.phone_number 104.568k (+/-14.7%) i/s (within noise) --- lib/faker.rb | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/faker.rb b/lib/faker.rb index a5d7d472b9..c0c5708541 100644 --- a/lib/faker.rb +++ b/lib/faker.rb @@ -58,6 +58,9 @@ class Base ULetters = Array('A'..'Z') LLetters = Array('a'..'z') Letters = ULetters + LLetters + # Frozen digit strings, used to avoid allocating a new string for each + # random digit generated by #numerify. + DIGITS = %w[0 1 2 3 4 5 6 7 8 9].freeze class << self attr_reader :flexible_key @@ -66,9 +69,15 @@ class << self ## by default numerify results do not start with a zero def numerify(number_string, leading_zero: false) - return number_string.gsub('#') { rand(10).to_s } if leading_zero - - number_string.sub('#') { rand(1..9).to_s }.gsub('#') { rand(10).to_s } + leading = !leading_zero + number_string.gsub('#') do + if leading + leading = false + DIGITS[rand(1..9)] + else + DIGITS[rand(10)] + end + end end def letterify(letter_string)