惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
Check Point Blog
U
Unit 42
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Martin Fowler
Martin Fowler
L
LangChain Blog
博客园_首页
博客园 - 【当耐特】
Vercel News
Vercel News
I
InfoQ
GbyAI
GbyAI
爱范儿
爱范儿
D
DataBreaches.Net
Blog — PlanetScale
Blog — PlanetScale
B
Blog RSS Feed
A
About on SuperTechFans
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
G
Google Developers Blog
大猫的无限游戏
大猫的无限游戏
Apple Machine Learning Research
Apple Machine Learning Research
F
Fortinet All Blogs
N
Netflix TechBlog - Medium
酷 壳 – CoolShell
酷 壳 – CoolShell
P
Proofpoint News Feed
美团技术团队
V
V2EX
Stack Overflow Blog
Stack Overflow Blog
有赞技术团队
有赞技术团队
Y
Y Combinator Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
H
Help Net Security
Recent Announcements
Recent Announcements
Microsoft Azure Blog
Microsoft Azure Blog
D
Docker
宝玉的分享
宝玉的分享
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
量子位
小众软件
小众软件
J
Java Code Geeks
S
SegmentFault 最新的问题
Engineering at Meta
Engineering at Meta
Google DeepMind News
Google DeepMind News
MongoDB | Blog
MongoDB | Blog
The Cloudflare Blog
Recorded Future
Recorded Future
阮一峰的网络日志
阮一峰的网络日志
T
The Blog of Author Tim Ferriss
MyScale Blog
MyScale Blog
Microsoft Security Blog
Microsoft Security Blog

Hacker News

Introducing Claude Opus 4.7 Qwen Studio The Future of Everything is Lies, I Guess: Where Do We Go From Here? GitHub - SeanFDZ/macmind: Single-layer transformer in HyperTalk for the classic Macintosh Show HN: Agent-cache – Multi-tier LLM/tool/session caching for Valkey and Redis Moving a large-scale metrics pipeline from StatsD to OpenTelemetry / Prometheus GitHub - Nightmare-Eclipse/RedSun: The Red Sun vulnerability repository GitHub - SethPyle376/hiraeth: Local AWS emulator focused on fast integration testing, with SQS support, SQLite-backed state, and a debug-friendly web UI. GitHub - macOS26/Agent: Any AI, replaces Claude Code, Cursor, OpenClaw. Over 18 LLM providers (Claude, OpenAI, Gemini, Ollama, Zai, HF, Qwen) wired into a native Mac app that writes code, builds Xcode projects, bumps versions, manages git, automates Safari, use AppleScript, JS or Accessibility, extend Agent! w/ MCP Servers, run tasks from your iPhone via Messages. YouTube now lets you turn off Shorts I Made a Terminal Pager Burgers | マクドナルド公式 Commands — HackerNews CLI documentation ChatGPT for Excel PiCore - Raspberry Pi Port of Tiny Core Linux Live Nation illegally monopolized ticketing market, jury finds Google Broke Its Promise to Me. Now ICE Has My Data. Founding Engineer at Adaptional | Y Combinator CRISPR takes important step toward silencing Down syndrome’s extra chromosome GitHub - saffron-health/libretto: The AI toolkit for building reliable browser automations US v. Heppner (S.D.N.Y. 2026) no attorney-client privilege for AI chats [pdf] Unexpected €54k billing spike in 13 hours: Firebase browser key without API restrictions used for Gemini requests Retrofitting JIT Compilers into C Interpreters IPv6 – Google The Accursèd Alphabetical Clock Cybersecurity Looks Like Proof of Work Now Fragments: April 14 Cal.com Goes Closed Source: Why AI Security Is Forcing Our Decision | Cal.com - Scheduling Software for Online Bookings Laravel raised money and now injects ads directly into your agent When moving fast, talking is the first thing to break Too much Discussion of the XOR swap trick – Heather Cafe Introduction to Spherical Harmonics for Graphics Programmers The Grand Line Building a Z-Machine in the worst possible language High-Level Rust: Getting 80% of the Benefits with 20% of the Pain GitHub - duguyue100/midnight-captain: Inspired by Midnight Commander, tailored to my taste. How to build a `git diff` driver · Jamie Tanna | Software Engineer Center for Responsible, Decentralized Intelligence at Berkeley The Local Universe’s Expansion Rate Is Clearer Than Ever, but Still Doesn’t Add Up - A new synthesis of astronomical measurements confirms a persistent mismatch that could point to physics beyond current models The air throughout our homes is infused with microplastics. But there are things you can do to breathe less of them The disturbing white paper Red Hat is trying to erase from the internet – OSnews The Future of Everything is Lies, I Guess: Annoyances ‘Abhorrent’: the inside story of the Polymarket gamblers betting millions on war Productive procrastination — Max van IJsselmuiden maps, territory and LMs 447 Terabytes per Square Centimetre at Zero Retention Energy: Non-Volatile Memory at the Atomic Scale on Fluorographane Show HN: Pardonned.com – A searchable database of US Pardons 20 Years on AWS and Never Not My Job The Seasons are Wrong Artemis II crew splashes down near San Diego after historic moon mission We gave an AI a 3 year retail lease in SF and asked it to make a profit | Andon Labs How a dancer with ALS used brainwaves to perform live On filing the corners off my MacBooks Installing every* Firefox extension OpenClaw’s memory is unreliable, and you don’t know when it will break Steve Blank Nowhere Is Safe Chimpanzees in Uganda locked in vicious 'civil war', say researchers watgo - a WebAssembly Toolkit for Go linux/Documentation/process/coding-assistants.rst at master · torvalds/linux GitHub - callumlocke/json-formatter: Makes JSON easy to read. Founding Product Engineer at Bild AI | Y Combinator A compelling title that is cryptic enough to get you to take action on it GitHub - Keychron/Keychron-Keyboards-Hardware-Design: Industrial design files for Keychron keyboards and mice. 100+ models with CAD assets in STEP, DXF, DWG, and PDF. Source-available, with commercial use allowed for original compatible accessories within the license terms. [ANNOUNCE] WireGuardNT v0.11 and WireGuard for Windows v0.6 Released 1D-Chess Helium Is Hard to Replace Cooperative Vectors Introduction | Evolve Keeping a Postgres queue healthy — PlanetScale Our response to the Axios developer tool compromise Do Americans read print books, e-books or audiobooks more? The Zettelkasten Method in Obsidian: A Practical Setup Guide Artemis II Is Competency Porn and We Are Starving For It WeakC4 Flight Viz — Cockpit View A Mexican surveillance giant you’ve never heard of is now watching the U.S. border Surelock: Deadlock-Free Mutexes for Rust RISC-V 101 – what is it and what does it mean for Canonical? | Ubuntu The Problem That Built an Industry How Much Linear Memory Access Is Enough? | Solidean Investigating Split Locks on x86-64 Simplest hash functions Sybilproof reputation mechanisms (2005) [pdf] What is a property? How Complex is my Code? Static code analysis in Kotlin — tools overview Toffoli gates are all you need PGLite evangelism dcmake: a new CMake debugger UI Clojure on Fennel part one: Persistent Data Structures Fragments: April 2 Python Release Python install manager 26.1 The Life and Death of the Book Review - Liberties Introducing Database Traffic Control — PlanetScale Bitcoin miners are losing $19,000 on every BTC produced as difficulty drops 7.8% God sleeps in the minerals Building slogbox Apple Silicon and Virtual Machines: Beating the 2 VM Limit Who was “Not Even Wrong” first? Pokemon Evolution Vs Darwinian Evolution The APL Programming Language Source Code
A new hash table for Lwan — L. Pereira's blog
2026-05-07 · via Hacker News

A new hash table for Lwan2026-05-06

For a long time, Lwan used to use a heavily modified version of the hash table from the kmod project. I was lightly involved with that project during its inception, so this seemed like a natural choice. Over the years, that hash table proved inneficient for the use cases in Lwan, so I began patching it to try and improve its performance; I succeeded in some cases, but the added complexity and years of technical debt eventually caught up with me, so things started failing.

I've wanted to try a different way of implementing a hash table, possibly using some kind of linear probing method, for a good while, so that's what I did.

Inspiration

I've been inspired by Rust's hashbrown, which has been the default hash table in its standard library for a good while. That hash table, in turn, was inspired by Abseil's SwissTable. Even the Go programming language began using a variation of this hash table since 2025.

I wanted to make Lwan's new hash table as portable as possible; however, Swiss Tables require the use of SIMD instructions to perform acceptably. As you can see in the following diagram, a swiss table is laid out like so:

  • It can have multiple groups, chained together;
  • Each group contains a Control Word and the slots containing the actual items;
  • The nth byte in the Control Word represents a Control Byte for the nth slot.

The result of the hash function is split in two: H1, which chooses the first group where an item – either empty, unused, or deleted; and H2, which is stored in the Control Word. SIMD is used to determine which slots in a group matches the required H2 (or an empty or a deleted slot), in parallel, with minimal instructions.

Control bytes in a Swiss Table
MeaningBinary
Empty0b10000000
Deleted0b11111110
Sentinel0b11111111
Full0b0xxxxxxx

Even though it performs a linear probe, it does so in a very efficient manner that's not only cache efficient, it's also using hardware capabilities that are rarely exploited in data structures.

Note

While H2 has only 7 bits, each byte in the control word uses the whole 8 bits to denote if a particular slot is empty, deleted, is a sentinel, or is in use. The distinction between Empty, Deleted, and Sentinel are implementation details that are better understood by watching the talk by Matt Kulukundis at CppCon 2017.

The hash table in Lwan

Using SIMD directly was ruled out due to it not being portable enough; even though SWAR could be used where SIMD was not available (or in architectures I wasn't familiar with or had the hardware to test the implementation). In its place, the standard C function memchr() was used instead. This function is usually implemented using SIMD instructions anyway (although with higher fixed costs that wouldn't be an issue if SIMD was used directly, as it'll become clear with code snippets down below).

As a result, instead of having multiple groups like Swiss Tables, the table in Lwan has only one group:

With this structure, the hash H is split into two:

  • uint8_t tophash = no_tombstone(H & 0xff), with no_tombstone() returning a value that's not used for a tombstone (\0);
  • uint32_t startpos = (H >> 8) & (capacity - 1), with capacity being always a power of two.

With both tophash and startpos in hand, the table is effectively split in two:

Making it possible to define the internal hash table probing functions like this:

static bool hash_probe_half(const struct hash *ht,
                           const void *key,
                           uint32_t *out_slot,
                           uint32_t startpos,
                           uint32_t endpos,
                           uint8_t tophash)
{
    uint8_t *slotptr =
        memchr(ht->tophashes + startpos, tophash, endpos - startpos);

    assert(tophash != '\0'); /* '\0' is the tombstone */

    while (slotptr) {
        uint32_t slot = (uint32_t)(slotptr - ht->tophashes);
        if (LIKELY(ht->key_equal(ht->buckets[slot].key, key))) {
            *out_slot = slot;
            return true;
        }
        if (UNLIKELY(endpos == slot)) {
            break;
        }
        slotptr = memchr(slotptr + 1, tophash, endpos - slot - 1);
    }

    return false;
}

static bool hash_probe_startpos(const struct hash *ht,
                                const void *key,
                                uint32_t *out_slot,
                                uint32_t startpos,
                                uint8_t tophash)
{
    return hash_probe_half(ht, key, out_slot, startpos, ht->cap, tophash) ||
           hash_probe_half(ht, key, out_slot, 0, startpos, tophash);
}

static bool
hash_probe(const struct hash *ht, const void *key, uint32_t *out_slot)
{
    const uint32_t hash = ht->hash(key);
    const uint32_t startpos = (hash >> 8) & (ht->cap - 1);
    return hash_probe_startpos(ht, key, out_slot, startpos,
                               no_tombstone(hash & 0xff));
}

The user-facing API can be implemented in term of these functions; for instance, here's how one looks a value from a key:

void *hash_find(const struct hash *ht, const void *key)
{
    uint32_t slot;

    return LIKELY(hash_probe(ht, key, &slot)) ? (void *)ht->buckets[slot].value
                                              : NULL;
}

Likewise, insertion and deletion are implemented in terms of hash_probe_half(). You may have noticed that this table makes no distinction between empty and deleted items, which, in one hand, simplifies things quite a bit, but in the other hand, makes the lookup theoretically bound to O(capacity).

During testing, though, I found out that lookups rarely check the second half, and that the key equality function is very likely to succeed the first time. I suspect that this happens because the chosen hash function (FNV-1a or the Intel CRC32 instructions depending on hardware capability) are not only pretty good for this particular hash table implementation, but their results are well used, both as a hint to where to start looking, and also what to look for.

Deleting items, however, require the costly operation of rehashing the whole table. Here's how element deleting is currently implemented:

int hash_del(struct hash *ht, const void *key)
{
    uint32_t slot;

    if (LIKELY(hash_probe(ht, key, &slot))) {
        /* Item found! Let's remove it by tombstoning it. */
        ht->tophashes[slot] = '\0';
        ht->free_key((void *)ht->buckets[slot].key);
        ht->free_value((void *)ht->buckets[slot].value);
        ht->len--;

        if (ht->cap > INITIAL_CAP && ht->len < ht->cap / 2) {
            /* Failure to resize to reduce the table won't leave it
             * in an inconsistent state, so don't propagate the error.
             */
            hash_resize(ht, ht->cap / 2);
        }
        return 0;
    }

    return -ENOENT;
}

The hash_resize() function doesn't work in-place, which isn't ideal. I've been experimenting with ideas to perform both in-place-rehashing and lazy-rehashing, but for now I'm going with the obvious but high-latency implementation until I'm happy with the experiments.

The foreach macro

A nice little addition to the hash table API is the HASH_FOREACH() macro that lets one loop through all keys/values stored in the table in a straightforward and fool-proof manner:

const void *k, *v;

HASH_FOREACH(table, &k, &v) {
    printf("%s: %s\n", (char *)k, (char *)v);
}

This is used both internally, and in the mimegen executable built during the Lwan build process.

Self-tests

This implementation now carries unit tests that execute automatically every time the server boots when built in debug mode. The macro is declared using the SELF_TEST() macro I wrote about a while back. This has already caught a few bugs in experiments I've made, and made it possible for me to refactor the implementation without having to worry too much about subtly breaking things.

Performance tests (or lack thereof)

While I usually talk about performance when talking about Lwan, the purpose of the hash table rewrite was not to make it more efficient – so I haven't measured how this new tables fares against the old one. (Although it's very likely that this new table performs better than the one it replaces.)

I'm just happy it's a much simpler implementation, without multiple sub-tables and the extra overhead of carrying the whole hash around with every key/value pair.

Future work

This table is mostly done, but I'll continue tweaking it here and there.

As I already mentioned, I'm experimenting with ways to make rehashing less expensive than it currently is. If I find a good way to implement this without requiring an extra level of indirection like the Go version of this hash table, I'll end up writing about it because it might be useful for other people.

Using SIMD instructions directly, with memchr() as a fallback, is also a route I might take some day. The hash_probe_half() function, as shown above, can be replaced with such a version without having to rethink the whole hash table.

Source code

The source code, as always, is in the Lwan repository.