みたいなタイトルで何か書いてみようと思ったら、既に先駆者が居られるよう(対象領域が違うけど)。
以下適当な散文。
言いたかったこと
マルチスレッド並列化においては、「Lock/Unlock処理だけの反復処理で速度性能を計測し、同期機構の優劣を評価する」とか「並列処理ライブラリの性能評価と称して、数列の総和演算のような軽量タスクを用いて議論する」といった、マイクロベンチマーク結果にはほとんど意味がない。それどころか、真に受けて現実のマルチスレッド処理に素直に適用すると、おそらく予想に反する悲惨な結果になるだろう。
「Lock/Unlock処理だけの反復処理で速度性能を計測し、同期機構の優劣を評価する」
はこんなの:
// 計測開始
for (/*100万回くらいループ*/) {
lock(mutex);
unlock(mutex);
}
// 計測終了
Lock/Unlock処理の目的は複数スレッド間の排他制御であり、単一スレッド動作での評価に正当な理由がない。単に“軽い”Lock/Unlock処理を良しとするなら、ユーザーランドで完結するスピンロック機構が自明な最適解となるだろう。このようなスピンロック処理はOSカーネルスケジューラの外側で行われること、またマルチコア環境でキャッシュコヒーレンスプロトコルの輻輳が予想されるため、実タスク処理へ単純適用すると深刻なスケーラビリティ問題を引き起こすことが想像に難くない。
一般的にLock/Unlock処理自体の処理負荷は、そもそも並列化の対象とするタスク処理負荷に比べて無視できるほど十分小さい。もしそうでないなら、そもそもロックの粒度、すなわち並列処理におけるデータ分割やタスク分割といった並列化構造の設計が適切でない。そのようなユースケース向け代替案としては、アトミックな変数操作も一つの選択肢である。
排他制御機構の選択においては、並列に処理されるタスク間の論理的な相互作用、OSによるスレッドスケジューリング、メモリキャッシュ制御などに着目すべきであり、マイクロベンチマーク評価結果に振り回されるべきでない。
「並列処理ライブラリの性能評価と称して、数列の総和演算のような軽量タスクを用いて議論する」
はこんなの:
// 計測開始
int sum = 0;
#pragma omp parallel for reduction(+:sum)
for (int i = 0; i < /*大きな値*/; i++) {
sum += i;
};
// 計測終了
まさに並列化処理設計のアンチパターンそのもの。並列処理には常に並列化オーバヘッドが存在する。オーバーヘッドがゼロコストという無意味な仮定を置くならば、単に最小単位までタスク分解を行えば、理論上は最大パフォーマンス・最大スケーラビリティをもたらすだろう。
現実世界においては、常にタスク負荷とオーバーヘッド負荷のトレードオフ問題であり、並列化対象のタスク粒度には大きな関心を払うべきである。実際の対象問題からかけ離れたタスクを用いて評価を行うことに何ら意味がなく、このような不適切なマイクロベンチマーク評価結果に振り回されるべきでない。
みたいな話。
オチは特にない。