Benchmarks
One benchmark, the shape of a web request, run against browser and Agentry, with and without YJIT. The table below is rendered from docs/benchmarks.json, which the benchmark writes.
Per request
Ruby 4.0.9, Apple M4 Max, measured on 2026-11-04: browser 6.2.0 at dd88933, Agentry 1.0.0 at e8bdc86. Each figure is the median of 7 runs.
| User agents | JIT | browser 6.2.0 µs | Agentry 1.0.0 µs | Faster | Objects |
|---|---|---|---|---|---|
| 2000 distinct user agents | YJIT | 125.84 | 25.36 | 5.0× | 245 → 52 |
| 20 user agents, repeated | YJIT | 114.02 | 2.01 | 56.7× | 217 → 12 |
| 2000 distinct user agents | none | 141.73 | 42.05 | 3.4× | 245 → 52 |
| 20 user agents, repeated | none | 133.64 | 3.79 | 35.3× | 217 → 12 |
bench/request.rb asks each user agent what a web request asks: the browser’s name and full version, bot? and the bot’s name, the platform’s name and version, device.mobile?, device.tablet? and the first accepted language. It runs over 2,000 distinct user agents sampled from the corpus, more than the cache holds, so that row measures a user agent seen for the first time; then over 20 of them repeated, as a site’s regular visitors are.
Why it is faster
A cache per user agent. Every answer but Accept-Language depends on the user agent alone. Each process keeps, for the last 1,000 distinct user agents, the browser class Browser.new chose, the platform, the device and the bot answers, computed lazily; the last 1,000 Accept-Language headers are parsed once too. About 1 KB per user agent, so 1 MB per worker. Browser.cache_size changes the size, 0 turns it off, and changing a matcher list or the bot list empties it.
Bots in one walk. browser matches the user agent against its bot list one entry at a time. Agentry walks a byte trie of the list, five times longer since the weekly sync, once per user agent, and the built-in bot matchers no longer build a second Browser for the same user agent.
Less work on a new user agent. Browser.new skips, unbuilt, a browser whose required pattern fails, and platform and device matchers are built only when a request asks for them.
Reproduce
Run it without JIT and with --yjit, for upstream and for Agentry, one at a time on an otherwise idle machine. path/to/upstream is a plain clone of fnando/browser at dd88933.
ruby --yjit -I path/to/upstream/lib bench/request.rb
ruby --yjit -I lib bench/request.rb
--write records the run in docs/benchmarks.json, with the gem, its commit, the Ruby, YJIT, the machine and the date, replacing the previous run of that gem and JIT. rake bench runs all four and renders this page and the home page from the file:
bundle exec rake bench UPSTREAM=path/to/upstream