My favourite small hash table

2025/12/09 23:47

My favourite small hash table

RSS: https://news.ycombinator.com/rss

要約

Japanese Translation:

記事では、32ビットキーと値を1つの64ビットスロットに詰めたコンパクトなロビン・フッドハッシュテーブルについて説明しています(下位32ビット=キー、上位32ビット=値)。ゼロスロットは空のエントリを示します。
テーブル構造は、スロット配列へのポインタ、32ビットマスク(

length-1
)、および非空スロット数を保持します。
検索は
(key + d) & mask
から始まり、空スロットに到達するか
Score(idx, slot_key) < d
が真になるまで走査し、低ビットへ回転した格納値を返します。
挿入はキーの追加または上書きを行い、ロードファクタが75 % を超えると(
count*4 >= mask*3
)、再ハッシュでサイズを倍増させます(
new_mask = old_mask * 2 + 1
)。置き換えられたスロットは
table_reinsert
によって移動されます。
削除は、次のスロットを左へシフトし、空スロットまたは
Score == 0
のスロットに到達するまで続け、墓石(tombstone)を避けます。
反復処理は配列を歩き、非ゼロスロットごとにビジターコールバックを呼び出します。キーに可逆的な32ビットハッシュ(例:CRC32/CRC32C)が適用される場合、その逆関数が反復時に使用されます。
キーまたは値が32ビットを超える場合、設計では32ビットハッシュやバイトバッファへのオフセットでインデックス付けされた別のキー/値配列を使用でき、より大きいまたは可変サイズのデータも扱えます。
この軽量で墓石のないテーブルは高速な検索と低メモリオーバーヘッドを提供し、データベースエンジンやキャッシュ層などのパフォーマンス重視システムに適しています。

本文

私はハッシュテーブルの設計と実装、特に線形探索(linear probing)を採用したロビン・フッド方式(Robin Hood)で、表サイズが2 のべき乗になるケースに興味があります。以下ではコアアイデアを実践的に見ていきます。


前提条件

  1. キーは 32‑bit 整数の均一乱数です。
  2. 値も 32‑bit 整数です。
  3. キー
    0
    が存在する場合、その値は必ず非ゼロです。
  4. テーブルは 32 GiB を超えません。

この前提条件を満たせば、キー/バリューのペアは 64‑bit 単語に収まります:下位 32 ビットがキー、上位 32 ビットが値です。
(3) が保証する「

key == 0 && value == 0
」という実際のエントリーは存在しないため、64‑bit 値
0
を空スロットとして利用できます。


テーブル構造

struct hash_table_t {
    uint64_t *slots;   // スロット配列
    uint32_t mask;     // (length-1) = 2^n - 1(長さは 2 のべき乗)
    uint32_t count;    // 占有スロット数
};

mask
を使うことで
index = key & mask
が得られます。
ロードファクタを 100 % 未満に保つために、
count < length
を保証し、
count
は 32 ビットで十分です。


探索(Lookup)

キーがランダムなため、キー

K
の「自然位置」は単純に
(K & mask)
です。
そのスロットが占有されていれば、線形探索を続けます:
(K+1)&mask
,
(K+2)&mask
…。

ロビン・フッドのルール は次のようになります。
任意のスロット

S
が保存されたキーの連鎖に含まれる場合、

Score(S.index, S.key) ≥ Score(S.index, K)

ここで

Score(index, key) = (index - key) & mask

です。

探索時には、自然位置からの距離

d
を保持します。
空スロットに到達したか、あるいは現在のスコアが
d
より小さいスロットを見つけた場合、そのキーは存在しません。

uint64_t table_lookup(hash_table_t *table, uint32_t key) {
    uint32_t mask  = table->mask;
    uint64_t *slots = table->slots;

    for (uint32_t d = 0; ; ++d) {
        uint32_t idx  = (key + d) & mask;
        uint64_t slot = slots[idx];

        if (slot == 0)
            return 0;                           // 存在しない

        if (key == (uint32_t)slot)
            return (slot >> 32) | (slot << 32); // 存在、値を取得(回転)

        if (((idx - (uint32_t)slot) & mask) < d)
            return 0;                           // キーはここにあり得ない
    }
}

x86‑64 や arm64 上ではこのコードは非常に軽量です。

slots[idx]
はアドレス生成命令一つ、下位 32 ビットの比較はネイティブ、32 ビット回転も1 命令で済みます。
riscv64 では
Zba/Zbb
拡張に依存しますが、なければスロットレイアウトを書き換える必要があります。


挿入(Insertion)

挿入は同じ探索パターンをたどります。キーが既に存在すれば上書きし古い値を返します。
現在の距離より小さいスコアを持つスロットに到達したら、そのスロットへ交換し、置換されたペアを再挿入(

table_reinsert
)します。

uint64_t table_set(hash_table_t *table, uint32_t key, uint32_t val) {
    uint32_t mask  = table->mask;
    uint64_t *slots = table->slots;
    uint64_t kv   = key + ((uint64_t)val << 32);

    for (uint32_t d = 0; ; ++d) {
        uint32_t idx  = ((uint32_t)kv + d) & mask;
        uint64_t slot = slots[idx];

        if (slot == 0) {                     // 空 – 挿入
            slots[idx] = kv;
            break;
        }

        if ((uint32_t)kv == (uint32_t)slot) { // 既存キー
            slots[idx] = kv;
            return (slot >> 32) | (slot << 32);
        }

        uint32_t d2 = (idx - (uint32_t)slot) & mask;
        if (d2 < d) {                        // 交換して置換ペアを再挿入
            slots[idx] = kv;
            table_reinsert(slots, mask, slot, d2);
            break;
        }
    }

    /* 75 % 超過時にリサイズ */
    if (++table->count * 4ull >= mask * 3ull)
        table_rehash(table);

    return 0;
}

table_reinsert
は軽量ヘルパーで、空スロットが見つかるまで探索を続けます。カウントは更新しません。

void table_reinsert(uint64_t *slots, uint32_t mask,
                    uint64_t kv, uint32_t d) {
    for (;; ++d) {
        uint32_t idx  = ((uint32_t)kv + d) & mask;
        uint64_t slot = slots[idx];

        if (slot == 0) {                     // 空 – 完了
            slots[idx] = kv;
            break;
        }

        uint32_t d2 = (idx - (uint32_t)slot) & mask;
        if (d2 < d) {                        // 交換して続行
            slots[idx] = kv;
            kv = slot;
            d  = d2;
        }
    }
}

table_rehash
はテーブルサイズを倍にし(新マスクは
old_mask*2+1
)、すべてのエントリーを再挿入します。


削除(Removal)

ロビン・フッドスコアを利用しているため、墓碑(tombstone)は不要です。
キーを削除するときは、その後ろにあるスロットを左へシフトし続け、空スロットまたはスコア

0
のスロットが出るまで繰り返します。これで連鎖の性質が保たれます。

uint64_t table_remove(hash_table_t *table, uint32_t key) {
    uint32_t mask  = table->mask;
    uint64_t *slots = table->slots;

    for (uint32_t d = 0; ; ++d) {
        uint32_t idx  = (key + d) & mask;
        uint64_t slot = slots[idx];

        if (slot == 0)
            return 0;                           // 存在しない

        if (key == (uint32_t)slot) {             // 見つかった
            uint32_t nxt = (idx + 1) & mask;
            --table->count;

            while (slots[nxt] && ((slots[nxt] ^ nxt) & mask)) {
                slots[idx] = slots[nxt];
                idx   = nxt;
                nxt   = (idx + 1) & mask;
            }
            slots[idx] = 0;                       // 最終空スロット
            return (slot >> 32) | (slot << 32);
        }

        if (((idx - (uint32_t)slot) & mask) < d)
            return 0;                           // 存在しない可能性が高い
    }
}

イテレーション(Iteration)

配列を線形スキャンし、ゼロでないスロットだけを訪問関数へ渡します。

void table_iterate(hash_table_t *table,
                   void (*visit)(uint32_t key, uint32_t val)) {
    uint64_t *slots = table->slots;
    uint32_t mask   = table->mask;

    for (uint32_t idx = 0; ; ++idx) {
        uint64_t slot = slots[idx];
        if (slot != 0)
            visit((uint32_t)slot, (uint32_t)(slot >> 32));

        if (idx == mask)
            break;
    }
}

拡張(Extensions)

ランダムでないキー

挿入・検索・削除の前に可逆ハッシュ

h(key)
を適用します。
ハッシュが等しければキーも等しいので、探索はそのままです。イテレーション時のみ逆ハッシュを使って元のキーを復元します。

uint32_t u32_hash(uint32_t h) {
    h ^= h >> 16;
    h *= 0x21f0aaad;
    h ^= h >> 15;
    h *= 0x735a2d97;
    h ^= h >> 15;
    return h;
}
uint32_t u32_unhash(uint32_t h) {
    h ^= h >> 15; h ^= h >> 30;
    h *= 0x97132227;
    h ^= h >> 15; h ^= h >> 30;
    h *= 0x333c4925;
    h ^= h >> 16;
    return h;
}

大きなキー/値

別にキー/バリュー配列を保持し、テーブルには 32‑bit ハッシュ+インデックスだけを格納します。

hash == 0
を禁止するか、
index+1
を保存すれば (3) は維持できます。探索時はハッシュ一致後に実際のキー比較が必要です。

可変長キー/値

シリアライズされたペアをバイトバッファに格納し、テーブルには 32‑bit ハッシュとそのオフセットだけを保持します。


結論

線形探索で 2 のべき乗サイズのロビン・フッドハッシュテーブルは:

  • コンパクト – エントリごとに 64‑bit 単語、墓碑不要。
  • 高速 – 現代 CPU 上で数命令程度、インデックス計算も簡単。
  • 拡張しやすい – ハッシュ関数の変更、大きなキー/値、可変長データも自然に統合可能。

同時実行性(lock‑free)や SIMD を活かした設計ではありませんが、多くのシングルスレッドワークロードで優れた性能と低メモリオーバーヘッドを提供します。

同じ日のほかのニュース

一覧に戻る →

2025/12/10 0:00

Show HN: Gemini Pro 3 hallucinates the HN front page 10 years from now

## Japanese Translation: ## 改訂版まとめ この記事では、最近のさまざまな技術的マイルストーンとリリースをレビューしています。 1. **SpaceX**:Starship HLS‑9 が Sea of Tranquility からテレメトリーを正常に送信しました。 2. **Linux Kernel**:バージョン 7.4 に完全な上流化された 100 % Rust カーネルが含まれています。 3. **開発者プラクティス**:生のコードを書くこととコンパイラプロンプトを使用することを対比した議論があります。 4. **人工知能**:LLaMA‑12 7B モデルは WebAssembly を介してコンタクトレンズ上で動作し、YC シード付きスタートアップ Nia は自律型コーディングエージェントを導入しています。 5. **教育ツール**:AlgoDrill は LeetCode パターンの強化に向けたインタラクティブなプラットフォームを提供します。 6. **エネルギー研究**:ITER の炉は 20 分間連続で正のエネルギー収支を達成しました。 7. **ハードウェア修理**:2024 年製 Framework Laptop を復元する方法を詳細に説明したレトロスペクティブガイドがあります。 8. **クラウドサービス**:Google は Gemini Cloud Services の提供を終了します。 9. **グラフィックスプログラミング**:WebGPU 2.0 により第 5 次元の可視化が可能になりました。 10. **Linux ディストリビューション**:Debian がバージョン 18 “Trixie” をリリースしました。 11. **プログラミング言語提案**:`sudo` ユーティリティを Zig で書き直す提案が提示されています。 この記事はこれらの出来事を文脈に置き、各々が過去の成功にどのように基づいているかを指摘し、その進歩が今後の開発実践、AI 統合、およびクラウドサービス戦略に影響を与えることを示唆しています。

2025/12/10 2:08

PeerTube is recognized as a digital public good by Digital Public Goods Alliance

## Japanese Translation: ### Summary PeerTubeはプラットフォームに依存しない多言語対応のビデオホスティングおよびライブストリーミングソリューションで、正式にデジタル公共財(DPG ID0092472)として承認されています。2025年10月7日にRicardo Torresによるレビューが行われ、Platform Independence評価を含むすべてのDPG標準要件を満たしていることが確認されました。これにより、ベンダーロックインなしで任意のインフラ上で実行できることが保証されています。ソースコードはGitHub(`github.com/Chocobozzz/PeerTube`)とFramagit(`framagit.org/framasoft/peertube`)で公開されており、透明性とコミュニティの貢献を確保しています。PeerTubeは44以上の言語(エスペラント、アラビア語、中国語簡体字・繁体字、日本語、韓国語、スペイン語、フランス語、ドイツ語、イタリア語、ポルトガル語、ロシア語など)をサポートし、フランスの教育省からイタリアの国家研究評議会、複数のドイツ大学、Blender、Debianプロジェクト、および活動家グループまで、多様な組織で既に利用されています。承認は2026年10月7日まで有効であり、毎年自己申告による使用状況更新が求められます。この推奨は、機関やオープンソースコミュニティが分散型ビデオプラットフォームを採用することを促進し、メディアホスティングの風景をよりオープンで多言語化、ベンダー非依存な解決策へとシフトさせる可能性があります。

2025/12/10 5:33

Django: what’s new in 6.0

## Japanese Translation: --- ## Django 6.0 – 主なリリースハイライト *リリース日 2025‑12‑03* | エリア | 変更点 | 意義 | |------|--------------|----------------| | **テンプレート** | 新しい `{% partialdef %}` / `{% endpartialdef %}` タグで、テンプレートを再利用可能なフラグメントに分割し、インラインまたは別途レンダリングでき、htmx とも相性が良い。 | テンプレートの DRY 化、ボイラープレート削減、コンポーネント再利用の容易化。 | | **バックグラウンドタスク** | 組み込み「Tasks」フレームワーク: `@task` デコレーター + `Task.enqueue()` API。組み込みバックエンド(`ImmediateBackend`、`DummyBackend`)があり、実稼働ではサードパーティパッケージ(例: `django-tasks` の `DatabaseBackend`)が必要。 | 単純なジョブに対して外部タスクライブラリの必要性を排除しつつ、実稼働時の柔軟性は維持。 | | **セキュリティ** | `ContentSecurityPolicyMiddleware` を追加。 `SECURE_CSP` と `SECURE_CSP_REPORT_ONLY` で設定可能。ノンスは自動生成され、テンプレート内では `{% csp_nonce %}`(`nonce="{{ csp_nonce }}"`)で使用できる。 | 自動ノンス処理付きの CSP サポートを組み込み、XSS リスクをデフォルトで低減。 | | **メール** | Django のメール API が Python の `email.message.EmailMessage` を利用するように変更。 `EmailMessage.send()` は内部でこの API に変換し、インライン添付は `MIMEPart` オブジェクトで追加可能。 | 標準ライブラリ互換のモダンなメール処理と簡易的な添付サポート。 | | **Mail API の非推奨** | `django.core.mail` API ではキーワード専用引数が必須となり、位置オプションパラメータは警告を出し将来的にエラーになる。 | より明確な API 使用を促進し、コードの未来保証を実現。 | | **シェル** | 自動インポートが拡張され、 `settings`、`connection`、`models`、`reset_queries`、`functions`、`timezone` が追加。 | もっと高速で便利な対話型開発環境。 | | **ORM** | • 動的フィールド(`db_default`、`GeneratedField`)は `save()` 後に SQL RETURNING を通じてリフレッシュされる;未対応バックエンドでは次回アクセス時まで遅延。<br>• `StringAgg` 集約がすべてのサポートデータベースで動作;文字列リテラル区切りは `Value()` で包む必要。<br>• デフォルト主キー型が `BigAutoField` に変更され、新規プロジェクトでは `DEFAULT_AUTO_FIELD` を設定しなくなる。 | モデル定義の簡素化、パフォーマンス向上、大規模アプリでの PK 終了防止。 | | **テンプレート変数 & タグ** | `forloop.length` が追加され、ループ長にアクセス可能;更新された `querystring` タグは `?` で接頭辞し、複数位置引数を受け取り、重複時には優先順位でマージ。 | より表現力豊かなテンプレートロジックとクリーンなクエリ文字列処理。 | | **コミュニティ** | Django 6.0 に 174 人の貢献者が参加。リリースノートではアップグレード準備を強調し、開発者に新機能を試すよう呼びかける;今後のリリースでタスクバックエンドや CSP 機能が拡張される可能性。 | 健全なコミュニティサポートとさらなる改善へのロードマップを示唆。 | **まとめ:** Django 6.0 は、再利用可能なテンプレートフラグメント、オプションの組み込みタスクシステム、ノンス付き CSP ミドルウェア、モダン化されたメール API など、実務で即使える改善を提供します。さらに ORM の強化、シェルの利便性向上、旧パターンの非推奨により、ボイラープレート削減・セキュリティ向上・大規模プロジェクトの将来準備が実現されます。 ---