Skip to content

Commit d3d26a7

Browse files
Lazy load experiment results
Related #3199 Please refer to /experiments/lazy_loading doc for more details.
1 parent 5bd5b8e commit d3d26a7

52 files changed

Lines changed: 444 additions & 193 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ruby.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,4 @@ jobs:
5858
bundler-cache: true
5959

6060
- name: Run tests
61-
run: bundle exec rake test
61+
run: bundle exec rake test LAZY_LOAD=1

benchmark/load.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# frozen_string_literal: true
2+
3+
require 'benchmark/ips'
4+
5+
Benchmark.ips do |x|
6+
x.report('require') { system('ruby load_faker.rb') }
7+
x.report('lazyload') { system('LAZY_LOAD=1 ruby load_faker.rb') }
8+
9+
x.compare!(order: :baseline)
10+
end

benchmark/load_faker.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/usr/bin/env ruby
2+
# frozen_string_literal: true
3+
4+
if defined?(Faker)
5+
raise 'fake is already defined...'
6+
end
7+
8+
load('/Users/stefannibrasil/projects/faker/lib/faker.rb')

doc/internet/http.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
# Faker::Internet::HTTP
1+
# Faker::HTTP
22

33
Available since version next.
44

55
# Keyword arguments: group
6+
67
```ruby
7-
Faker::Internet::HTTP.status_code #=> 418
8-
Faker::Internet::HTTP.status_code(group: :information) #=> 102
9-
Faker::Internet::HTTP.status_code(group: :successful) #=> 200
10-
Faker::Internet::HTTP.status_code(group: :redirect) #=> 306
11-
Faker::Internet::HTTP.status_code(group: :client_error) #=> 451
12-
Faker::Internet::HTTP.status_code(group: :server_error) #=> 502
13-
```
8+
Faker::HTTP.status_code #=> 418
9+
Faker::HTTP.status_code(group: :information) #=> 102
10+
Faker::HTTP.status_code(group: :successful) #=> 200
11+
Faker::HTTP.status_code(group: :redirect) #=> 306
12+
Faker::HTTP.status_code(group: :client_error) #=> 451
13+
Faker::HTTP.status_code(group: :server_error) #=> 502
14+
```

doc/music/music.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,17 @@
33
Available since version 1.6.4.
44

55
```ruby
6-
Faker::Music.key #=> "C"
6+
Faker::Song.key #=> "C"
77

8-
Faker::Music.chord #=> "Amaj7"
8+
Faker::Song.chord #=> "Amaj7"
99

10-
Faker::Music.instrument #=> "Ukelele"
10+
Faker::Song.instrument #=> "Ukelele"
1111

12-
Faker::Music.band #=> "The Beatles"
12+
Faker::Song.band #=> "The Beatles"
1313

14-
Faker::Music.album #=> "Sgt. Pepper's Lonely Hearts Club"
14+
Faker::Song.album #=> "Sgt. Pepper's Lonely Hearts Club"
1515

16-
Faker::Music.genre #=> "Rock"
16+
Faker::Song.genre #=> "Rock"
1717

18-
Faker::Music.mambo_no_5 #=> "Monica"
18+
Faker::Song.mambo_no_5 #=> "Monica"
1919
```

experiments/lazy_loading.md

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# Lazy load experiment results
2+
3+
Branch: sb-ta/lazy-load-experiment
4+
Date: February 10th, 2026
5+
Owner(s): Stefanni Brasil and Thiago Araujo
6+
7+
## Impact
8+
9+
Using `const_missing` to lazy load generators was an idea from talking with Jeremy Evans, who kindly responded our questions about improving faker's performance.
10+
11+
### Changes needed
12+
13+
#### Namespace changes
14+
15+
A few generators had namespaces that didn't match their file name, and most of them were manageable, but two needed to be renamed:
16+
17+
- `Faker::Music` -> `Faker::Song`. `Faker::Music` is a nested namespace.
18+
- `Faker::Internet::HTTP` -> `Faker::HTTP`. `Faker::Internet` is another generator.
19+
20+
Considering that only two generators would be renamed, it wouldn't be a huge burden for users to use this approach.
21+
22+
#### File location changes
23+
24+
To prevent other generators from erroring out due to namespace clashing, some generators have to be moved around (ex. `Faker::Quote` was moved from `/faker/quotes/quote` to `faker/default/quote`). Users can still use the generators as before, their namespaces didn't change.
25+
26+
### Benefits
27+
28+
- no additional dependencies needed
29+
- code is extremely faster
30+
- after changing these two generators, which would be breaking changes, we can enable this as an opt-in configuration
31+
32+
## Results
33+
34+
profiler:
35+
36+
[bundle exec vernier run -- ruby -e "require 'faker'" LAZY_LOAD=1](https://share.firefox.dev/3ZuCP55)
37+
[bundle exec vernier run --interval 100 --allocation-interval 10 -- ruby -e "require 'faker'; Faker::Internet.email" LAZY_LOAD=1](https://share.firefox.dev/4601PoA)
38+
39+
benchmarks (Machine specs: Apple M1 Pro 16GB memory on MacOS Sequoia 15.7.3.)
40+
41+
```sh
42+
benchmark % ruby require.rb
43+
took 250.0249999575317ms to load
44+
```
45+
46+
```sh
47+
benchmark % ruby load.rb
48+
ruby 3.3.10 (2025-10-23 revision 343ea05002) [arm64-darwin24]
49+
Warming up --------------------------------------
50+
require 1.000 i/100ms
51+
lazyload 1.000 i/100ms
52+
Calculating -------------------------------------
53+
require 5.874 (± 0.0%) i/s (170.25 ms/i) - 30.000 in 5.115652s
54+
lazyload 12.207 (± 8.2%) i/s (81.92 ms/i) - 61.000 in 5.007059s
55+
56+
Comparison:
57+
require: 5.9 i/s
58+
lazyload: 12.2 i/s - 2.08x faster
59+
```
60+
61+
## Artifacts
62+
63+
### Scripts
64+
65+
Constants were registered using this script:
66+
67+
```ruby
68+
# lazy_load.rb
69+
70+
CATEGORIES = {
71+
Blockchain: 'blockchain',
72+
Books: 'books',
73+
Creature: 'creature',
74+
Default: 'default',
75+
Fantasy: 'fantasy',
76+
Games: 'games',
77+
JapaneseMedia: 'japanese_media',
78+
Locations: 'locations',
79+
Movies: 'movies',
80+
Music: 'music',
81+
Quotes: 'quotes',
82+
Religion: 'religion',
83+
Sports: 'sports',
84+
Travel: 'travel',
85+
TvShows: 'tv_shows'
86+
}.freeze
87+
88+
def template(key)
89+
"# frozen_string_literal: true
90+
91+
module Faker
92+
class #{key}
93+
if ENV['LAZY_LOAD'] == '1'
94+
Faker.lazy_load(self)
95+
end
96+
end
97+
end"
98+
end
99+
100+
CATEGORIES.each do |key, value|
101+
File.write(File.join('lib', 'faker', "#{value}.rb"), template(key))
102+
end
103+
```

lib/faker.rb

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,43 @@ def disable_enforce_available_locales
275275
end
276276
end
277277
end
278+
279+
if ENV['LAZY_LOAD'] == '1'
280+
def self.load_path(*constants)
281+
constants.map do |class_name|
282+
class_name
283+
.to_s
284+
.gsub('::', '/')
285+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
286+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
287+
.tr('-', '_')
288+
.downcase
289+
end.join('/')
290+
end
291+
292+
def self.lazy_load(klass)
293+
def klass.const_missing(class_name)
294+
load_path = case class_name
295+
when :DnD
296+
Faker.load_path('faker/games/dnd')
297+
else
298+
Faker.load_path(name, class_name)
299+
end
300+
301+
begin
302+
require(load_path)
303+
rescue LoadError
304+
require(load_path.gsub('faker/', 'faker/default/'))
305+
end
306+
307+
const_get(class_name)
308+
end
309+
end
310+
311+
lazy_load(self)
312+
end
278313
end
279314

280-
# require faker objects
281-
Dir.glob(File.join(mydir, 'faker', '/**/*.rb')).each { |file| require file }
315+
if ENV['LAZY_LOAD'] != '1'
316+
Dir.glob(File.join(mydir, 'faker', '/**/*.rb')).each { |file| require file }
317+
end

lib/faker/blockchain.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# frozen_string_literal: true
2+
3+
module Faker
4+
class Blockchain
5+
if ENV['LAZY_LOAD'] == '1'
6+
Faker.lazy_load(self)
7+
end
8+
end
9+
end

lib/faker/books.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# frozen_string_literal: true
2+
3+
module Faker
4+
class Books
5+
if ENV['LAZY_LOAD'] == '1'
6+
Faker.lazy_load(self)
7+
end
8+
end
9+
end

lib/faker/creature.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# frozen_string_literal: true
2+
3+
module Faker
4+
class Creature
5+
if ENV['LAZY_LOAD'] == '1'
6+
Faker.lazy_load(self)
7+
end
8+
end
9+
end

0 commit comments

Comments
 (0)