<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/speeding-up-small-ruby-hashes-44cvuew6g" -->

---
title: Speeding Up (small) Ruby Hashes | daily.dev
description: An in-depth walkthrough of optimizing Ruby's small-hash lookup path (the ar_table used for hashes with up to 8 entries), which previously relied on an O(n)...
canonical: https://daily.dev/posts/speeding-up-small-ruby-hashes-44cvuew6g
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: Speeding Up (small) Ruby Hashes | daily.dev
og:description: An in-depth walkthrough of optimizing Ruby's small-hash lookup path (the ar_table used for hashes with up to 8 entries), which previously relied on an O(n)...
og:url: https://daily.dev/posts/speeding-up-small-ruby-hashes-44cvuew6g
og:image: https://api.daily.dev/og/posts/44CVUeW6G.png
og:image:alt: Speeding Up (small) Ruby Hashes
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Speeding Up (small) Ruby Hashes

**[RUBYLAND](https://daily.dev/sources/rubyla)** · 20 min read · 0 upvotes · 0 comments

## Summary

An in-depth walkthrough of optimizing Ruby's small-hash lookup path (the ar_table used for hashes with up to 8 entries), which previously relied on an O(n) linear scan through 1-byte hash hints. Using a SWAR (SIMD within a register) technique adapted from the classic 'find a NULL byte in a 64-bit word' trick, the author builds a bitmask-based search that finds matching hint bytes in constant time. After working through endianness issues, mutation hazards from Object#eql? callbacks, and branch-predictor-friendly specialization of the first-match case, the resulting patch makes ar_table lookups roughly constant-time regardless of key position and consistently faster than the full st_table implementation, with a pending Ruby pull request implementing the change.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://byroot.github.io/ruby/performance/2026/08/13/speeding-up-ruby-hashes.html>

## Questions this post answers

### How does Ruby's ar_table store hashes with 8 or fewer entries internally?

Ruby's ar_table is not a true hash table but a flat array of key-value pairs, up to RHASH_AR_TABLE_MAX_SIZE (8) entries, paired with an 8-byte ar_hint array storing only the lowest byte of each key's hash code to save memory. Lookups previously scanned this array linearly, an O(n) operation, before comparing full keys with eql?.

_Developers tuning Ruby hot paths can follow ongoing Ruby internals work like this on daily.dev._

### What is the SWAR technique for finding a matching byte in a 64-bit word?

SWAR (SIMD within a register) finds a target byte across an 8-byte word by XORing the word against a repeated mask of the target byte, then using the classic null-byte-detection trick: mask off high bits, add 0x7f per byte, OR back the original, OR again with the mask, then invert. Zero bytes in the XOR result surface as set high bits, letting ctz/nlz locate the match's index in constant time instead of scanning byte by byte.

_Engineers exploring low-level bit-manipulation tricks can track deep-dive performance writeups like this via daily.dev._

### How much faster is Ruby's optimized ar_table hash lookup after applying SWAR-based search versus the original linear scan?

Benchmarks on a Ruby 4.1.0dev build show the SWAR-based patch making hint lookups roughly constant time across all 8 slots (about 14M i/s regardless of position, versus the original linear scan's 1.58x slowdown from first to last key), and misses became about 1.5x faster (16M vs 9-11M i/s). The patched ar_table also became consistently faster than the full st_table implementation, reversing the prior space-versus-time tradeoff.

_Anyone benchmarking Ruby internals changes can follow performance-focused Ruby posts like this on daily.dev._

## Similar posts on daily.dev

- [Shrinking Ruby Hashes](https://daily.dev/posts/shrinking-ruby-hashes-3nmqm1br3) · RUBYLAND · 0 upvotes · 0 comments
- [Ruby Hash Isn’t Always a Hash Table \(And Why That Matters\)](https://daily.dev/posts/ruby-hash-isn-t-always-a-hash-table-and-why-that-matters--xz895trui) · Ruby Flow · 1 upvotes · 0 comments
- [When Your Hash Becomes a String: Hunting Ruby’s Million-to-One Memory Bug](https://daily.dev/posts/when-your-hash-becomes-a-string-hunting-ruby-s-million-to-one-memory-bug-nhlgsnql3) · Closer to Code · 0 upvotes · 0 comments
- [My favourite small hash table](https://daily.dev/posts/my-favourite-small-hash-table-ijftuqxho) · Hacker News · 0 upvotes · 0 comments
- [Medium](https://daily.dev/posts/medium-wmj5wwwzi) · Medium · 1 upvotes · 0 comments

---

Tags: [#performance](https://daily.dev/tags/performance), [#hardware](https://daily.dev/tags/hardware), [#ruby](https://daily.dev/tags/ruby), [#data-structures](https://daily.dev/tags/data-structures)

[View this post on daily.dev](https://daily.dev/posts/speeding-up-small-ruby-hashes-44cvuew6g)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"Speeding Up (small) Ruby Hashes","url":"https://daily.dev/posts/speeding-up-small-ruby-hashes-44cvuew6g","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/speeding-up-small-ruby-hashes-44cvuew6g"},"datePublished":"2026-08-13T13:58:53.815Z","dateModified":"2026-08-13T13:59:24.726Z","description":"An in-depth walkthrough of optimizing Ruby's small-hash lookup path (the ar_table used for hashes with up to 8 entries), which previously relied on an O(n)...","image":"https://media.daily.dev/image/upload/s--ZrL_HSsR--/f_auto/v1722860399/public/Placeholder%2006","thumbnailUrl":"https://media.daily.dev/image/upload/s--ZrL_HSsR--/f_auto/v1722860399/public/Placeholder%2006","isAccessibleForFree":true,"articleSection":"RUBYLAND","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"RUBYLAND","logo":"https://media.daily.dev/image/upload/t_logo,f_auto/v1/logos/rubyla","url":"https://daily.dev/sources/rubyla"},"commentCount":0,"discussionUrl":"https://daily.dev/posts/speeding-up-small-ruby-hashes-44cvuew6g","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":0},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":0}],"keywords":"performance,hardware,ruby,data-structures","timeRequired":"PT20M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"RUBYLAND","item":"https://daily.dev/sources/rubyla"},{"@type":"ListItem","position":3,"name":"Speeding Up (small) Ruby Hashes"}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/speeding-up-small-ruby-hashes-44cvuew6g#faq","mainEntity":[{"@type":"Question","name":"How does Ruby's ar_table store hashes with 8 or fewer entries internally?","acceptedAnswer":{"@type":"Answer","text":"Ruby's ar_table is not a true hash table but a flat array of key-value pairs, up to RHASH_AR_TABLE_MAX_SIZE (8) entries, paired with an 8-byte ar_hint array storing only the lowest byte of each key's hash code to save memory. Lookups previously scanned this array linearly, an O(n) operation, before comparing full keys with eql?. Developers tuning Ruby hot paths can follow ongoing Ruby internals work like this on daily.dev."}},{"@type":"Question","name":"What is the SWAR technique for finding a matching byte in a 64-bit word?","acceptedAnswer":{"@type":"Answer","text":"SWAR (SIMD within a register) finds a target byte across an 8-byte word by XORing the word against a repeated mask of the target byte, then using the classic null-byte-detection trick: mask off high bits, add 0x7f per byte, OR back the original, OR again with the mask, then invert. Zero bytes in the XOR result surface as set high bits, letting ctz/nlz locate the match's index in constant time instead of scanning byte by byte. Engineers exploring low-level bit-manipulation tricks can track deep-dive performance writeups like this via daily.dev."}},{"@type":"Question","name":"How much faster is Ruby's optimized ar_table hash lookup after applying SWAR-based search versus the original linear scan?","acceptedAnswer":{"@type":"Answer","text":"Benchmarks on a Ruby 4.1.0dev build show the SWAR-based patch making hint lookups roughly constant time across all 8 slots (about 14M i/s regardless of position, versus the original linear scan's 1.58x slowdown from first to last key), and misses became about 1.5x faster (16M vs 9-11M i/s). The patched ar_table also became consistently faster than the full st_table implementation, reversing the prior space-versus-time tradeoff. Anyone benchmarking Ruby internals changes can follow performance-focused Ruby posts like this on daily.dev."}}]}
```

