hashbrown

SwissTable アルゴリズム(Google の absl::flat_hash_map)の Rust 移植。Rust 1.36 以降、std::collections::HashMap / HashSet の内部実装そのものである。独立クレートとして公開されており、std にはない追加機能を提供する。

開発者は Amanieu d’Antras 氏。リポジトリは rust-lang org 配下に移管されている。

なぜ hashbrown を直接使うのか

std の HashMap は hashbrown だが、std 経由では使えない機能がある:

機能std HashMaphashbrown
デフォルトハッシャーSipHash 1-3 (HashDoS 耐性)foldhash (高速、HashDoS 耐性なし)
no_std不可可能 (alloc のみ必要)
カスタムアロケータnightly allocator_api のみstable でも可能 (allocator-api2)
entry_ref APIなしあり (借用キーでエントリアクセス)
HashTableなしあり (低レベル安全 API)
rayon 並列イテレータなしrayon feature
serde 対応なしserde feature

hashbrown を直接使う主な動機は「速度」「no_std」「拡張 API」のいずれか。

主要な型

HashMap<K, V, S, A>

std の HashMap のドロップイン代替。追加のジェネリックパラメータ A でカスタムアロケータを受け取れる。

use hashbrown::HashMap;

let mut map = HashMap::new();
map.insert("key", 42);
assert_eq!(map.get("key"), Some(&42));

HashSet<T, S, A>

HashMap<T, (), S, A> のラッパー。

HashTable<T, A>

v0.14.2 で導入。Hash / Eq を型に実装していなくても使える低レベル安全 API。ハッシュ値と比較関数をクロージャとして外から渡す。

use hashbrown::{HashTable, DefaultHashBuilder};
use std::hash::BuildHasher;

let mut table = HashTable::new();
let builder = DefaultHashBuilder::default();
let hasher = |val: &i32| builder.hash_one(val);

// 挿入: ハッシュ値とリハッシュ用クロージャを明示的に渡す
table.insert_unique(hasher(&1), 1, |v| hasher(v));
table.insert_unique(hasher(&2), 2, |v| hasher(v));

// 検索: ハッシュ値と等価判定クロージャを渡す
let found = table.find(hasher(&1), |&v| v == 1);
assert_eq!(found, Some(&1));

用途:

RawTable<T, A> (非推奨)

unsafe な生テーブル API。v0.15 で非推奨化、raw-entry feature でのみ有効。新規コードでは HashTable を使うべき。

entry_ref API

std の entry() は所有キーを要求する。hashbrown の entry_ref() は借用キーでアクセスし、キーが存在しない場合にのみ所有権変換が発生する:

use hashbrown::HashMap;

let mut map: HashMap<String, Vec<usize>> = HashMap::new();

// std の entry(): 毎回 String を生成してしまう
// map.entry("hello".to_string()).or_insert_with(Vec::new);

// hashbrown の entry_ref(): &str で検索、挿入時のみ String 化
map.entry_ref("hello").or_insert_with(Vec::new).push(1);
map.entry_ref("hello").or_insert_with(Vec::new).push(2);
// ↑ 2回目: "hello" は既に存在するので String アロケーションは発生しない

ホットパスで entry を多用する場合、この差がパフォーマンスに直結する。

デフォルトハッシャーの変遷

時期ハッシャー特徴
初期FxHashFirefox 由来。高速だがアライン値で品質問題
v0.7〜v0.14ahash高速。AES-NI / fallback 対応。FxHash の品質問題を解消
v0.15〜現在foldhashahash より更に高速。品質も良好

注意: foldhash は HashDoS 耐性を提供しない。 ネットワーク由来のキー(ユーザー入力、HTTP ヘッダ等)を扱う場合は SipHash を明示的に指定すべき:

use hashbrown::HashMap;
use std::hash::RandomState;

// ネットワーク由来のキーには SipHash を使う
let mut map: HashMap<String, i32, RandomState> =
    HashMap::with_hasher(RandomState::new());

no_std 対応

組み込みやカーネル等、std が使えない環境でも alloc クレートさえあれば動作する:

#![no_std]
extern crate alloc;

use hashbrown::HashMap;

fn example() {
    let mut map = HashMap::new();
    map.insert("sensor_id", 42);
}

カスタムアロケータ

allocator-api2 feature(デフォルト有効)により、stable Rust でもカスタムアロケータが使える:

use hashbrown::HashMap;

// A パラメータでカスタムアロケータを指定
let map: HashMap<String, i32, _, MyArenaAllocator> =
    HashMap::new_in(MyArenaAllocator::new());

アリーナアロケータや共有メモリアロケータ等と組み合わせて、メモリ配置戦略を制御できる。

rayon 連携

rayon feature で並列イテレータが使える:

use hashbrown::HashMap;
use rayon::prelude::*;

let map: HashMap<i32, String> = (0..100_000)
    .map(|i| (i, format!("val_{i}")))
    .collect();

// 並列イテレーション
let even_count = map.par_iter()
    .filter(|(k, _)| *k % 2 == 0)
    .count();

Feature Flags (v0.17)

featureデフォルト説明
default-hasher有効foldhash をデフォルトハッシャーとして組み込む
allocator-api2有効stable でカスタムアロケータ対応
equivalent有効Equivalent トレイトによるカスタム比較
inline-more有効追加のインライン最適化
rayon無効並列イテレータ
serde無効Serialize / Deserialize 実装
nightly無効#[may_dangle] 等の nightly 機能
raw-entry無効非推奨 RawEntry API

hashbrown の内部実装 (SwissTable 固有の差異)

hashbrown は SwissTable の設計に忠実だが、Rust 固有の最適化がある:

メモリレイアウト

[Padding] T_last ... T1 T0 | C0 C1 ... C_last [Mirror]
          ← データ (逆順) → ← 制御バイト →   ← 末尾ミラー →

SIMD バックエンド選択

// コンパイル時に自動選択される
cfg_if! {
    if #[cfg(target_arch = "x86_64")] {
        // SSE2 (x86_64 では常に利用可能)
        // Group::WIDTH = 16
    } else if #[cfg(target_arch = "aarch64")] {
        // NEON
        // Group::WIDTH = 16
    } else {
        // ポータブル fallback (ビット演算)
        // Group::WIDTH = 8
    }
}

空テーブル最適化

let map: HashMap<String, i32> = HashMap::new();
// ↑ ヒープアロケーションゼロ。静的 EMPTY 配列を参照するのみ
// 最初の insert() でアロケーションが発生

バージョン履歴の重要な変更

バージョン変更
v0.1初回リリース。SwissTable の Rust 移植
v0.7デフォルトハッシャーを FxHash → ahash に変更
v0.14.0allocator-api2 対応。ARM NEON 最適化。Equivalent トレイト導入
v0.14.2HashTable 型を導入
v0.15.0破壊的変更多数。 RawEntry 非推奨化、RawTable を private 化、HashTable が推奨に。デフォルトハッシャーを foldhash に変更
v0.16.0foldhash 0.2.0 に更新
v0.17.0MSRV 1.85。Entry API 拡張 (into_map, into_table 等)
v0.17.1最新安定版

押さえどころ(カード化候補)

関連

Graph