
2026/06/03 5:00
CおよびC++ APIを実装したブランチレスクイックソートが、std::sortやpdqsortよりも高速
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
blqsort は、高度な分岐なし(branchless)手法を用いて分岐誤予測によって引き起こされるハードウェアの遅延を最小化することにより、標準的な C++
std::sort を大幅に上回る高性能な単一ヘッダーソートライブラリです。Apple M1 システムでのベンチマーク結果(Clang -O3 でコンパイル)では、blqsort は 5,000 万個の二重精度数値を 0.97 秒でソートし、これに対し std::sort および pdqsort では 1.33 秒かかりました。また、独自に定義した構造体エントリ 5,000 万個のソートには blqsort で 0.96 秒、一方 std::sort では 3.46 秒かかりました。本ライブラリは C および C++ の両方をサポートし、単スレッドおよびマルチスレッドモードを選択可能となるよう、4 つの独立したヘッダーファイル(blqs.h、blqs_thr.h、blqsort.h、blqsort_thr.h)として提供されています。GCC もしくは Clang でコンパイル可能です。信頼性と速度を確保するため、blqsort は 1024 要素スタック配列を補助バッファとして使用し、大規模な分割区間に対して median-of-medians を用いたピボットとループアンローリングを適用し、小さなサブセットに対してカスタムソートネットワークを採用するとともに、悪い入力に対してはヒープソートへ、コピーコストが高いデータ型に対しては BlockQuicksort へ知的にフォールバックします。Google Highway のような外部依存関係を必要とする複雑な解決策と異なり、blqsort は適切なヘッダーを含めるのみで動作し、SIMD ライブラリを必要とせず、現代のハードウェア上でパフォーマンスクリティカルなアプリケーションへの堅牢かつ移植性の高いドロップイン代替手段として機能します。本文
ブランチレスクイックソートの概要と実装ガイド
1. パフォーマンスベンチマーク
5,000 万件の
double タイプデータをソートした際の比較結果(すべてシングルスレッド版を使用)です。
| メトロロジー | Apple M1 | AMD Ryzen |
|---|---|---|
| 1.33 秒 | 5.56 秒 |
| 1.33 秒 | 2.81 秒 |
| 0.97 秒 | 2.06 秒 |
補足事項
- Apple M1: マルチスレッド版はさらに 3〜4 倍 の高速化が可能です。
- ランタイム: C++ バージョンと C バージョンでは性能差が極めて僅かです。
- 環境: Clang (M1) および GCC (Ryzen) を
最適化でコンパイル。-O3
2. 技術的アプローチ
ブランチレスプログラミングの概念
現代のプロセッサにおいて、**分岐誤予測(branch misprediction)**を回避することが高速化の鍵です。
コード比較例:ブランチレス vs 通常
【非推奨】条件分岐を含む書き方(遅い)
for (int i = 0; i < 1000; i++) { if (numbers[i] < 500) { small_numbers[smlen] = numbers[i]; smlen += 1; } }
【推奨】ブランチレスアプローチ(高速)
for (int i = 0; i < 1000; i++) { small_numbers[smlen] = numbers[i]; smlen += (numbers[i] < 500); // 真偽値を直接足算する }
ソートネットワークの活用
小規模なサブセット(2〜12 要素)では、カスタムソートネットワークを採用します。
- メリット: ブランチレスな
素関数を使用し、極めて少ないスワップでソート可能。sort-2 - 戦略: 各サイズごとに専用コードパスを定義します。
ピボット選択と悪性入力への対処
- 不良入力回避: 同じ種の要素をグループ化し、バランスが崩れる部分をヒープソートに切り替えます。
- ソート済み判定: パーティションが既にソート済みかのチェックを行います。
- ピボット選択: 大規模領域では **Median-of-Medians(中央値の中位)**戦略を採用。
- 最適化: 重要なパーティションループについては明示的に**アンロール(展開)**しています。
補助バッファ戦略
fluxsort に由来する手法で、スタック上の 1024 要素分の領域(ヒープとは異なる)を「補助バッファ」として使用します。
- 片方の領域から 1024 要素をコピーしスペース確保。
- ブランチレス手法で左右交互に 1024 要素分をコピー。
注釈: コピー回数は本来必要な倍近く増えますが、軽量データ型においては「誤予測回避の利点」の方が圧倒的に効率的です。
データ型による実装の違い
- 軽量データ型: ブランチレス・バッファベース(
等)を使用。blqs.h - 重量級データ型 (文字列など):
でない場合はコピーコストが高くなるため、ブロック移動を削減した BlockQuicksort を使用します。std::is_trivially_copyable
3. システム別使用方法
C++ インターフェース
単一ヘッダーファイルのみで利用可能。
シングルスレッド版 (blqs.h
)
blqs.h#include "blqs.h" double data[SIZE]; blqs::sort(data, data + SIZE);
マルチスレッド版 (blqs_thr.h
)
blqs_thr.hC++ std::thread を使用した並列処理です。
// blqs.h の代わりに blqs_thr.h をインクルードしてください #include "blqs_thr.h" double data[SIZE]; blqs::sort(data, data + SIZE); // 関数呼び出し形式は同一
C インターフェース
#define メカニズムを用いてデータ型ごとに特殊化されたコードを生成します。
シングルスレッド版 (blqsort.h
)
blqsort.h#define BLQS_CMP(a, b) ((a) < (b)) #define BLQS_TYPE double #include "blqsort.h" double data[SIZE]; blqsort(data, SIZE);
マルチスレッド版 (blqsort_thr.h
)
blqsort_thr.hPOSIX threads を使用します。
// blqsort.h の代わりに blqsort_thr.h をインクルードしてください #include "blqsort_thr.h" double data[SIZE]; blqsort(data, SIZE); // 関数呼び出し形式は同一
4. カスタムデータ構造体のソート
Google Highway などの SIMD ライブラリは数値向けですが、複雑な構造体には適用しづらいため、柔軟性を確保したい場合は以下の方法を使用します。
C++ での例(コンパイル済み関数オブジェクト対応)
独自の比較ロジックを
operator< で定義可能。
#include "blqs.h" struct entry { int id; int value; bool operator<(const entry& other) const { return id < other.id; } }; struct entry data[SIZE]; blqs::sort(data, data + SIZE);
C での例(マクロによる比較関数定義)
マクロでカスタムフィールドを指定可能。
struct entry { int id; int value; }; #define BLQS_CMP(a, b) (((a).id) < ((b).id)) #define BLQS_TYPE struct entry #include "blqsort.h" struct entry data[SIZE]; blqsort(data, SIZE);
5. カスタム構造体パフォーマンス比較
独自の構造体(フィールドが重い場合)をソートした場合のベンチマーク結果です。
| 実装 | Apple M1 | AMD Ryzen |
|---|---|---|
| 3.46 秒 | 4.75 秒 |
| 3.46 秒 | 4.72 秒 |
| 0.96 秒 | 2.20 秒 |
6. リンク・参考資料
- 'if' がパフォーマンスを下げる場合は回避しましょう (詳細解説)
- インタラクティブなソートデモ
- お問い合わせ: christof.kaser@gmail.com