最近、私はポアソン分布 (amath_pdist) を計算する関数のマルチスレッド実装に取り組んでいました。目標は、ワークロードを複数のスレッドに分割して、特に大規模なアレイのパフォーマンスを向上させることでした。ただし、期待した速度向上を達成する代わりに、配列のサイズが増加するにつれて速度が大幅に低下することに気付きました。
いくつかの調査の結果、犯人は 誤った共有であることがわかりました。この投稿では、フォールス シェアリングとは何かを説明し、問題の原因となっている元のコードを示し、大幅なパフォーマンスの向上につながった修正を共有します。
偽共有は、複数のスレッドが共有配列の異なる部分で動作するが、それらのデータが同じキャッシュラインに存在する場合に発生します。キャッシュ ラインは、メモリと CPU キャッシュの間で転送されるデータの最小単位です (通常は 64 バイト)。 1 つのスレッドがキャッシュ ラインの一部に書き込むと、他のスレッドが論理的に独立したデータを処理している場合でも、そのラインは無効になります。この不必要な無効化は、キャッシュ ラインの繰り返しの再ロードにより大幅なパフォーマンスの低下を引き起こします。
これは私の元のコードの簡略版です:
void *calculate_pdist_segment(void *data) { struct pdist_segment *segment = (struct pdist_segment *)data; size_t interval_a = segment->interval_a, interval_b = segment->interval_b; double lambda = segment->lambda; int *d = segment->data; for (size_t i = interval_a; i < interval_b; i++) { segment->pdist[i] = pow(lambda, d[i]) * exp(-lambda) / tgamma(d[i] + 1); } return NULL; } double *amath_pdist(int *data, double lambda, size_t n_elements, size_t n_threads) { double *pdist = malloc(sizeof(double) * n_elements); pthread_t threads[n_threads]; struct pdist_segment segments[n_threads]; size_t step = n_elements / n_threads; for (size_t i = 0; i < n_threads; i++) { segments[i].data = data; segments[i].lambda = lambda; segments[i].pdist = pdist; segments[i].interval_a = step * i; segments[i].interval_b = (i == n_threads - 1) ? n_elements : (step * (i + 1)); pthread_create(&threads[i], NULL, calculate_pdist_segment, &segments[i]); } for (size_t i = 0; i < n_threads; i++) { pthread_join(threads[i], NULL); } return pdist; }
上記のコード内:
この問題は、大きな配列ではうまく対応できませんでした。境界の問題は小さいように見えるかもしれませんが、反復回数が膨大であるため、キャッシュの無効化のコストが増大し、数秒間の不必要なオーバーヘッドが発生します。
問題を解決するために、posix_memalign を使用して、pdist 配列が 64 バイト境界 に合わせて配置されていることを確認しました。これにより、スレッドが完全に独立したキャッシュ ラインで動作することが保証され、誤った共有が排除されます。
更新されたコードは次のとおりです:
double *amath_pdist(int *data, double lambda, size_t n_elements, size_t n_threads) { double *pdist; if (posix_memalign((void **)&pdist, 64, sizeof(double) * n_elements) != 0) { perror("Failed to allocate aligned memory"); return NULL; } pthread_t threads[n_threads]; struct pdist_segment segments[n_threads]; size_t step = n_elements / n_threads; for (size_t i = 0; i < n_threads; i++) { segments[i].data = data; segments[i].lambda = lambda; segments[i].pdist = pdist; segments[i].interval_a = step * i; segments[i].interval_b = (i == n_threads - 1) ? n_elements : (step * (i + 1)); pthread_create(&threads[i], NULL, calculate_pdist_segment, &segments[i]); } for (size_t i = 0; i < n_threads; i++) { pthread_join(threads[i], NULL); } return pdist; }
アライメントされたメモリ:
キャッシュライン共有なし:
キャッシュ効率の向上:
修正を適用した後、amath_pdist 関数の実行時間が大幅に減少しました。私がテストしていたデータセットでは、実測時間が 10.92 秒から 0.06 秒 に減少しました。
読んでいただきありがとうございます!
コードに興味がある人は、ここで見つけることができます
以上がマルチスレッドアプリケーションにおけるフォールスシェアリングを理解して解決する実際の問題の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。