Normally I just fire off a tweet when I spot a nice performance PR landing in Ruby. Lately I’ve been catching up on a backlog of Ruby performance work I’d bookmarked and never gotten around to – so some of what’s below isn’t brand new, with a few PRs dating back to 2025. There were so many of them – some headline-grabbing, some small but delightfully clever – that a thread won’t cut it. So here’s a roundup instead, both the recent landings and the ones I’m late to.
A few ground rules: every PR below ships a concrete benchmark number, so when I say “Nx faster” it’s the author’s own measurement, not vibes. Numbers come from different machines and workloads, so treat them as “here’s the win on the benchmark that motivated the change,” not cross-comparable lab results. Click through to any PR for the full picture – most authors document their methodology beautifully.
Let’s go.
String#scrub skips ASCII runs – Instead of decoding a string character-by-character, scrub now jumps over ASCII runs using the same search_nonascii trick valid_encoding? uses. On English HTML it’s up to 45.55x faster, on Japanese HTML 22.71x, and ~3.5x on the general case – with no regression on the worst case. Beautiful work by FletcherDares, who’s been on a string-performance tear.String#codepoints ASCII hot path – Same author, same instinct: add a local fast path for ASCII bytes inside mostly-ASCII UTF-8 strings. Result: ~1.9x faster on mixed ASCII content, neutral on pure multibyte.String#gsub! stops copying on no-match – gsub! was eagerly copying shared backing storage even when nothing matched. Defer that copy until the first real match (like sub! already does) and you get 2.33x faster no-match calls – and the allocation on a 100k-char shared string drops from 100,041 bytes to 40 bytes.byroot (Jean Boussier) went on a tear through Ruby’s file primitives, and the numbers are spicy:
File.join common case – Optimistically handle the common “two UTF-8 strings” case and scan backwards for the separator. Up to 18.81x faster for many-string joins, 7.80x for two strings.File.extname for common encodings – Skip multibyte handling for known-safe encodings. Up to 6.17x faster on long paths.File.expand_path single-byte fast path – A single-byte-encoding fast path nets 2.67x faster.Dir.scan yields entry type – Yield each child’s type straight from struct dirent‘s d_type, avoiding a separate stat per child. Recursive directory walks come out 2.12x faster (“twice as fast”).dir.c caches the working directory – Cache and cheaply revalidate pwd with a stack buffer instead of always heap-allocating. Up to 1.33x faster Dir.pwd on Linux.wb_unprotected bits clear for a whole 64-slot page at once during sweep. ~14% off object-new.rb_class_allocate_instance into gc.c – Also jhawthorn: relocating the function lets allocation helpers inline with newobj. ~10–15% faster Object.allocate (1.15x).Object.new (1.12x).TypedData_Get_Struct – byroot added an inlinable fast path to rb_check_typeddata, which makes Mutex#synchronize and Monitor#synchronize ~1.54x / ~1.55x faster respectively.Thread::Queue uses a ring buffer – Swapping the backing array for a ring buffer removes array-function overhead: ~23% faster (1.24x). byroot.strpbrk, a wyhash word-at-a-time constant pool, and a parser arena. ~22% faster parsing at roughly the same memory. (The matching ruby/ruby side is #16418.)compact_child_nodes with an each_child_node that yields directly. Visiting the Rails codebase came out ~21% faster on the interpreter and roughly 2.3x faster under YJIT.DefNode – Defer DefNode deserialization in the Java loader so JRuby/TruffleRuby don’t pay for method bodies up front: ~1.5x faster on the parsing-core metric.tompng (Tomoya Ishida) has been quietly doing extraordinary things to BigDecimal:
VpMult batch size – Bumping the divmod batch from 8 to 16 makes mid-size multiplications ~1.8x faster. tompng.BigDecimal#to_s – byroot replaced two snprintf calls with a lean integer-to-ASCII routine: ~2.6x faster for small numbers, ~3.8x for large ones.RCLASS_EXT_WRITABLE perf – luke-gruber swapped FL_TEST/FL_SET for their _RAW variants, dropping a YJIT getivar benchmark from 60ms to 40ms (~1.5x).Array#find in Ruby – swebb reimplemented Array#find in Ruby so the JIT can chew on it: ~1.96x faster under YJIT, neutral on the interpreter.guard_shape_failure side exits on the lobsters benchmark from 22.5% down to 3.0%, keeping more code in ZJIT.Float#nan? / finite? / infinite? lets it emit the fast C-call path: ~21–27% faster on those predicates in a tight loop.Method#call and adding an ArrayAset HIR instruction for array element assignment – each shaving a few percent off the relevant wall-clock benchmarks.A few more that are smaller in scope but very much worth a click – and a thank-you to each author:
Integer#to_s two-digit lookup table – emit two digits per loop iteration; up to ~33% faster on large Fixnums.NilClass methods moved to Ruby – Hartley McGuire made nil.to_c / to_r JIT-friendly: up to 3.5x faster.OPTIMIZED_CMP in r_less – speeds up Range#cover? / Range#overlap? by up to ~3x.rb_gc_declare_weak_references API trims WeakMap overhead: ~60% faster WeakMap#[]=.Array#pack with the 'w' format.parse.y processing from 2.84s to 1.60s (~1.78x).If you like performance magic, go read these. And if you maintain a gem, read them twice – a lot of what’s here (back-to-front scanning, single-byte fast paths, deferring copies, avoiding stat) is worth learning from.
Thanks to everyone credited here for the work.
The post Small PRs, big speedups: The Ruby performance work you almost missed appeared first on Closer to Code.
Canonical announces that the Advantech AOM-2721 is officially joining the list of Ubuntu Certified Hardware.…
Yet again instead of tweets, a blog post. The backlog got out of hand -…
This article provides a guide demonstrating how to deploy OpenProject on Ubuntu VPS. What is…
Canonical is pleased to announce that NVIDIA’s newly introduced NVIDIA Nemotron 3.5 Lightning, an open,…
If you have been using an ecommerce store to place online orders or have been…