22 ポイント 投稿者 GN⁺ 2025-01-02 | 2件のコメント | WhatsAppで共有
  • 「S+ツリー」という静的探索木を実装し、ソート済みデータの高速検索を実行
  • Algorithmica の記事で提案されたコードを出発点として最適化し、提案された追加アイデアや改善作業をコード化
  • アセンブリコードを分析した後、可能なあらゆる命令を最適化して性能を最大化
  • 複数のクエリを並列に処理してスループットを向上させるバッチングを導入
  • 目標は、S+ツリーによってソート済みデータで高いスループットを維持しながら効率的にクエリを実行すること

1. 紹介と動機

  • 問題の定義:

    • 入力: ソート済み 32 ビット符号なし整数リスト vals: Vec<u32>
    • 出力: できるだけ小さいサイズで、特定のクエリ (q) 以上の値を返すデータ構造
    • メトリクス: 1 秒あたりに処理可能な独立クエリ数
  • 目的:

    • バイオインフォマティクスにおける効率的なデータ構造の構築、特に DNA 配列インデックスのための接尾辞配列検索の高速化
    • CPU 性能と SIMD 命令を活用した性能最大化を目標とする
  • 参考資料:

    • 二分探索と配列レイアウトの研究(“Array Layouts for Comparison-Based Searching”)
    • S+ツリーと静的 B-木の紹介資料

2. S+ツリーと最適化

2.1 二分探索と Eytzinger レイアウト

  • Rust 標準ライブラリの二分探索はキャッシュ効率が低く、データサイズが増えると性能が低下
  • Eytzinger レイアウト:
    • 二分探索木を配列形式で保存し、キャッシュ活用を最大化
    • 性能: L3 キャッシュを超えるデータに対して二分探索より最大 4 倍高速

2.2 S+ツリーの概念

  • S-ツリー:
    • ノードごとに 15 個のソート済み値を含む 16 分木の形
    • B-木より効率的で、Eytzinger レイアウトより圧縮しやすい
  • S+ツリー:
    • すべてのデータをリーフノードに保存し、上位ノードにも重複して保存
    • 単純な検索と効率的な構造を提供

2.3 find 関数の最適化

  • 基本的な線形探索:
    • データを走査しながら条件に合う値を返す(非効率)
  • 自動ベクトル化:
    • 分岐のないコードに変換し、SIMD 命令の活用で性能が 2 倍向上
  • 手動 SIMD 実装:
    • 手動で SIMD 命令を活用して最適化し、5 命令で性能を最大化

3. バッチングとプリフェッチ

3.1 バッチング (Batching)

  • 複数のクエリを並列に処理し、メモリ待ち時間を相殺
  • バッチサイズを増やすことでスループットを改善し、最大バッチサイズ 16 で飽和

3.2 プリフェッチ (Prefetching)

  • 次のノードをあらかじめメモリに取り込み、L3 キャッシュを超えるデータで性能を向上
  • バッチングと組み合わせ、処理時間を 45ns/query から 30ns/query に短縮

4. データレイアウトと構造の最適化

4.1 ノードサイズの変更

  • ノードごとの値を 15 個に減らして乗算処理を単純化し、キャッシュ効率を向上
  • L3 キャッシュ内のデータに対してわずかな性能向上

4.2 メモリレイアウトの変更

  • レイアウトを逆順で保存したり、パディングを最小化した構成を試験
  • 逆順レイアウトとパディング削減レイアウトのいずれも性能への大きな影響はなし

5. データパーティショニング

5.1 接頭辞ベースのパーティショニング

  • データの上位ビットを基準にパートを分割
  • 小規模データでは性能が向上するが、メモリオーバーヘッドが発生

5.2 圧縮サブツリー

  • 各サブツリーをパックして空間効率を向上
  • パートのサイズ追跡が必要で、クエリ速度はやや低下

6. マルチスレッド比較

  • 6 スレッド使用時、クエリ時間を 27ns から 7ns に短縮
  • RAM 帯域幅の制限がボトルネックになる

7. 結論と今後の研究

  • 二分探索と比べて 40 倍以上の性能向上(1150ns/query → 27ns/query)
  • 今後の課題:
    • データバランスの最適化と RAM アクセスの削減
    • 範囲クエリおよびソート済みクエリの処理
    • 接尾辞配列検索の統合

2件のコメント

 
beenzinozino 2025-01-04

いやあ…さまざまな言語の組み込みライブラリに適用されたら、その波及効果はかなり大きい気がします。

 
GN⁺ 2025-01-02
Hacker Newsの意見
  • Rust がアルゴリズム関連の低レベルなコンテンツでますます多く使われるようになっていると感じる。以前は C(++) や科学的な擬似コードで書かれたコンテンツが主流だった。Rust の利用が増えているのは好ましい

    • Rust には詳しくないが、Rust で書かれたコンテンツも理解できた。C で書かれたアルゴリズム例を Rust プログラマーが理解できるのと似ている
    • Rust が標準化されることを望んでおり、Zig もあわせて使われるとよいと思う
  • クエリをバッチに分ける方法が検討されていない。キャッシュ外テーブルの参照が主なコストである

    • 十分な数のクエリがあれば、ツリーの複数階層を一度に処理できる
    • 結果を正しい出力順に並べ替える必要が生じる可能性がある
  • int32 値の数はそれほど多くなく、完全なビットセットは 512MB で済む。一般的なデータ構造としては Roaring Bitmaps を勧める

    • 単純な参照だけが必要なら、最小完全ハッシュ関数を検討する価値がある
  • Rust で低レベル効率を高める方法には驚かされる。C++ 実装と比較してみたい

  • Eytzinger ツリーの利点は、ノードインデックスを中順走査位置に変換する公式があることだ

    • 基本のキー格納領域がソート済みキー集合である場合に有用である
    • Eytzinger を使うと、複数回のループ反復を事前に予測できる
  • 4GB メモリ上で u32 を検索するのに約 27ns のオーバーヘッドが発生するのは驚きだ

    • バッチサイズ 8 で最適化がどのように進むのか気になる
    • マルチスレッドの結果も興味深い。1 スレッドから 6 スレッドにするとオーバーヘッドが 4 倍改善している
  • Rust と C++ についての議論は多いが、Common Lisp で SIMD を維持しつつ実装する方法を考えてしまう

  • 低レベル最適化についての記事を読むたびに、著者が時間をかけてナノ秒を節約してくれたことに感謝する

    • こうした最適化が積み重なって、人類はどれほどの時間を節約してきたのだろうと思う
  • 1.7 の図 3 には誤りがあると思う。1.3 の図 1 の y 軸ラベルは「逆転送量」であるべきだと提案する

  • ときどき書き込みも必要なら、大きな静的検索ツリーと小さな書き込み可能ツリーを併用できる

    • 十分な変更がたまった時点で、その変更を静的ツリーの新しいバージョンへ反映させられる