|
| 1 | +# Lazy load experiment results |
| 2 | + |
| 3 | +- Git Branch: [sb-ta/lazy-load-experiment](https://github.com/faker-ruby/faker/compare/main...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 | +Constants were configured to be lazy loaded using this script: |
| 64 | + |
| 65 | +```ruby |
| 66 | +# lazy_load.rb |
| 67 | + |
| 68 | +CATEGORIES = { |
| 69 | + Blockchain: 'blockchain', |
| 70 | + Books: 'books', |
| 71 | + Creature: 'creature', |
| 72 | + Default: 'default', |
| 73 | + Fantasy: 'fantasy', |
| 74 | + Games: 'games', |
| 75 | + JapaneseMedia: 'japanese_media', |
| 76 | + Locations: 'locations', |
| 77 | + Movies: 'movies', |
| 78 | + Music: 'music', |
| 79 | + Quotes: 'quotes', |
| 80 | + Religion: 'religion', |
| 81 | + Sports: 'sports', |
| 82 | + Travel: 'travel', |
| 83 | + TvShows: 'tv_shows' |
| 84 | +}.freeze |
| 85 | + |
| 86 | +def template(key) |
| 87 | +"# frozen_string_literal: true |
| 88 | +
|
| 89 | +module Faker |
| 90 | + class #{key} |
| 91 | + if ENV['LAZY_LOAD'] == '1' |
| 92 | + Faker.lazy_load(self) |
| 93 | + end |
| 94 | + end |
| 95 | +end" |
| 96 | +end |
| 97 | + |
| 98 | +CATEGORIES.each do |key, value| |
| 99 | + File.write(File.join('lib', 'faker', "#{value}.rb"), template(key)) |
| 100 | +end |
| 101 | +``` |
0 commit comments