5

こんにちは仲間のプログラマー。すでに1 つの質問をしましたが、非常に良い回答が得られたにもかかわらず、問題を解決できませんでした。次に、時間をかけてコードをリファクタリングし、並列化の可能性を向上させました (計算バッチを減らし、それぞれの計算負荷を高くすることにより)。しかし、それでもシリアル処理よりも優れたパフォーマンスは得られません。

この並列処理の遅さは、コンテキストの切り替えによるものと思われます。または、共通オブジェクトの「自動」同期が原因である可能性があります。何が起こっているのかを理解するのを手伝ってくれると思います。

私の場合を述べさせてください。私は科学計算用のプログラムを作成しています。外部のものに依存するのではなく、開始時に与える入力値に依存します。この問題のサイズはNs(私が使用する名前です) で測定できます。これは解の「解像度」と見なすことができ、ユーザー入力の 1 つであり、通常は 100 程度です。

このように、メイン クラスに doubleys[Ns][N]やなどの double 配列がいくつかありますphiS[Ns][Nord][N]。ここで、N と Nord はプログラムの他の固定された大きさです。私のプログラムでは、Nsポイントごとにいくつかのことを計算する必要があり、ここで並列化が行われます。各点の計算は独立しているので、それらを別のスレッドに分割して、より高速になることを願っています。

そこで、ループを作成する代わりに、for (int i=0; i<Ns; <i++)この計算義務を Runnable バッチに分割しました。それぞれのバッチの範囲は、より小さな間隔: ですfor (int i=start; i<end; i++)。ここで、開始と終了は常に 0 から Ns の間です。たとえば、デュアル コア PC を使用している場合、2 つのバッチを作成します。1 つはstart = 0end = Ns/2で、もう1 つはstart = Ns/2end = Nsです。私がクアッドコアを使用している場合、2番目のバッチも必要start = Ns/4end = Ns/2なります(すべてのケースで分割が正確であると仮定して)。

Runnable を実装するクラスとしての各 Batch は、 に格納され、コアの数に等しいサイズArrayList<Batch>の に与えられます。FixedThreadPoolバッチを実行し、単純なCountDownスキームを使用して終了するのを待ちます。

このバッチのそれぞれは、プログラムのメイン クラスからこれらの配列のデータにアクセスする必要がありますが、それらのアクセスは、各バッチが からのみ読み取るyS[start][]ためyS[end][]、2 つのバッチが同じ配列要素を読み取ろうとすることは決してありません。各バッチが他のバッチと同じ要素にアクセスしようとしていないとしても、Java はまだ yS をロックするのではないかと思います。

私の問題は、各バッチが何千もの double を処理する必要があるため、コンテキストの切り替えによるオーバーヘッドに関連しているのか、プログラムの構築方法がそれに影響を与える可能性があるのか​​ も疑問に思います。

関連する配列の要素だけを各バッチに渡す方法を見つける必要があるかもしれませんが、これにアプローチする方法はわかりません。ポインターがあれば、単純なポインター操作で、何も再割り当てすることなく、必要な要素だけの新しい配列を作成できます。Javaでそのようなことを行う方法はありますか?

最後に、同期が必要なコードの一部 (他の配列を処理する部分) があり、それは既に正常に動作しています。上記で説明したこの計算の義務は、私のプログラムが行う唯一のことではありません。それらはループ内にあり、順次処理部分と交互になっていますが、合計実行時間としては非常に重要です。

要約すると、質問は次のとおりです。期待していたのに、なぜマルチスレッドで得られないのですか?

ここでプレーンシリアルとマルチスレッドプログラムを数回実行したところ、シリアルで14500ミリ秒、マルチスレッドで15651ミリ秒になりました。どちらも同じデュアル コア上にあります。その他の注意事項: シリアル実行では、各計算デューティ (0 から Ns) に約 1.1 から 4.5 ms かかります。デュアル スレッドから、各バッチ (Ns/2 ポイント) には約 0.5 ~ 3 ミリ秒かかります。(run()メソッドの上から下まで計測。数値収束の仕方により毎回の計算負荷が異なります)

ご清聴ありがとうございました。

4

4 に答える 4

2
 I wonder if Java still locks up yS, even that each batch isn't trying to access
 the same elements as others.

Java には自動同期やロックはありません。それを明示的にコーディングする必要があります。

I wonder also if my problem is related to the overhead due to context switching..

コンテキスト スイッチにはオーバーヘッドがあります。すべてのスレッドが、CPU を集中的に使用する同じタスクで動作する場合、スレッドの数は、使用可能なプロセッサ コアの数と同じにする必要があります。

If there were pointers, I could have new arrays of just the desired elements with
simple pointer operations and without reallocating anything.

Java のすべてのオブジェクトは、参照によって渡されます (たとえば、オブジェクトをメソッドに渡す場合)。そして、基本的にすべての参照はポインターです (逆参照できないという違いがあります)。したがって、コードで明示的に要求された場合を除き、Java ではオブジェクトはコピーされません。

そうは言っても、別のことに注意する必要があります。コレクション (リスト、ハッシュマップなど) に多くの要素を追加する場合は、このコレクションを拡張する必要があります。内部的には、すべてのコレクションは配列を使用して要素を格納します。要素が追加されると、配列のサイズを変更する必要があります。Java では配列のサイズを変更する方法がないため、新しい配列を作成し、古いオブジェクトへのすべての参照を新しい配列にコピーする必要があります。または、プリミティブ型を使用する場合は、すべてのデータをコピーする必要があります。したがって、コレクションを作成するときは、サイズを変更する必要がないように、適切なサイズにする必要があります。

また、Java プログラムでいくつのスレッドを使用する必要がありますか?もお読みください。

于 2011-02-24T14:33:28.953 に答える
2

実行している可能性のある 1 つの可能性は、スレッドがキャッシュ ラインをスラッシングすることです。異なるスレッドが同じキャッシュ ライン内の場所に急速に書き込む場合 (たとえば、同じ配列内で閉じる場合)、ハードウェアの通信オーバーヘッドが高くなり、データの一貫性が維持されます。

于 2011-02-24T14:23:21.273 に答える
1

今までのお話を踏まえて、以下のことをやってみたいと思います

  1. 配列のサイズを大きくするために、シリアル バージョンとパラレル バージョンの間で結果を比較します。パフォーマンスの違いは、問題のサイズにとって実際には重要ではない可能性があり、サイズが大きくなった場合、つまり配列のサイズが大きくなった場合にのみ表示される場合があります

  2. 各ランナブルに配列の独自のコピーを与えます。パフォーマンスに照らして、配列がメモリ内に配置される方法とそれらにアクセスする方法がパフォーマンスに影響を与える可能性があります。2D配列がある場合でも、配列の同時リストとしてメモリ内にシリアルに配置されます。したがって、この配列をランナブル間で共有すると、一部のランナブルでは非効率になる可能性があります。

于 2011-02-24T14:41:17.910 に答える
-1

複数のコレクションを作成し、作業の一意のコレクションを各スレッドに渡すのに十分なメモリがありますか?このようにして、同じメモリにアクセスする複数のスレッドの競合を完全に取り除くことができますか?

于 2011-02-24T14:41:30.997 に答える