はスレッドサブクラスの次の定義(全体の実行可能なJavaソース・ファイルは、あなたの便宜のため、質問の最後に含まれている)を守って説明:このプログラムは、-Dpar
のスレッドを開始し、各スレッドのsz
を-Dsize/-Dpar
に設定します。プログラム実行時には、コマンドラインで-Dsize
とが設定されます。各スレッドオブジェクトには、新しい1024
要素配列で初期化されたフィールドarray
があります。その理由は、異なる数のスレッド間で同じ量の作業を分割したいからです。プログラムの規模を拡大することを期待しています。配列の割り当てと、Java仮想マシンとメモリの競合にアクセス
各スレッドが開始され、すべてのスレッドが完了するのに必要な時間が測定されます。以下に示すように、JIT関連の影響に対抗するために複数の測定を行います。各スレッドはループを行います。ループ内では、スレッドは配列内の512
の位置の要素を偶数の反復で読み込み、奇数の反復で同じ要素を512
に書き込みます。局所変数のみが変更されます。
完全なプログラムは以下の通りです。
分析:-verbose:gc
でテスト
- このプログラムの実行中に発生するいかなるガベージコレクションはありません。
実行]コマンド:
java -Xmx512m -Xms512m -server -Dsize=500000000 -Dpar=1 org.scalapool.bench.MultiStackJavaExperiment 7
CASE 1:そのためには、1,2,4,8
スレッドの時間を実行している(7回の繰り返し):
>>> All running times: [2149, 2227, 1974, 1948, 1803, 2283, 1878]
>>> All running times: [1140, 1124, 2022, 1141, 2028, 2004, 2136]
>>> All running times: [867, 1022, 1457, 1342, 1436, 966, 1531]
>>> All running times: [915, 864, 1245, 1243, 948, 790, 1007]
私の考えは、非線形スケーリングは、メモリの競合によるものであるということでした。ちなみに、初期の反復は実際にはうまくいく - これは、異なる反復で配列が異なるメモリ領域に割り当てられるという事実のためかもしれない。
ケース2:次に、私はスレッドのrun
方法でFoo[] arr = array
行をコメントとrun
方法自体に新しい配列を割り当てる:Foo[] arr = new Foo[1024]
。測定:
>>> All running times: [2053, 1966, 2089, 1937, 2046, 1909, 2011]
>>> All running times: [1048, 1178, 1100, 1194, 1367, 1271, 1207]
>>> All running times: [578, 508, 589, 571, 617, 643, 645]
>>> All running times: [330, 299, 300, 322, 331, 324, 575]
今回は、すべてが予想どおりにスケールされます。私は配列が割り当てられた場所が何らかの役割を果たしているとは想像もしませんでしたが、明らかに何とかしています。私の考えは、以前は配列が互いに近くに配置されていて、メモリの競合が起き始めるということでした。
CASE 3:この仮定を検証するために、私は再びラインFoo[] arr = array
コメントをはずしてきましたが、今回はメモリ内の位置が十分に互いに離れているように書かれていることを確認するためにnew Foo[32000]
にarray
フィールドを初期化します。ここでは、スレッドオブジェクトの作成中に割り当てられた配列を再度使用していますが、CASE1との違いは配列が大きいことだけです。
したがって、メモリの競合が原因であるようです。
プラットフォーム情報:
Ubuntu Server 10.04.3 LTS
8 core Intel(R) Xeon(R) CPU X5355 @2.66GHz
~20GB ram
java version "1.6.0_26"
Java(TM) SE Runtime Environment (build 1.6.0_26-b03)
Java HotSpot(TM) 64-Bit Server VM (build 20.1-b02, mixed mode)
質問:これは明らかに、メモリ競合の問題です。しかし、なぜこれは起こっているのですか?
エスケープ解析は開始されていますか?その場合、CASE2の
run
メソッドで作成された配列全体がスタックに割り当てられていることを意味しますか?このランタイム最適化の正確な条件は何ですか?確かに、配列は100万要素のスタックに割り当てられていませんか?アレイが ヒープ上に割り当てられているのとは逆にスタックに割り当てられていても、CASE1の場合でも、異なるスレッドによる2つの配列アクセスを少なくとも512 * 4バイト= 2kbで分割する必要があります。 !これは、どんなL1キャッシュラインよりも明らかに大きいです。これらの影響が誤った共有によるものである場合、完全に独立したいくつかのキャッシュラインへの書き込みがパフォーマンスにどのように影響しますか? (ここでは、各配列がJVM上の連続したメモリブロックを占めていることを前提としていますが、配列の作成時に割り当てられます。インテルXeonはccNUMAアーキテクチャを持っているので、代わりにL1キャッシュを使用してください - 私が間違っていれば私を訂正してください)
個々のスレッドが独自の新しいオブジェクトを独自に割り当てる場合は、配列がスレッドに割り当てられているときの競合がより少なくなる原因は何ですか?もしそうなら、参照が共有されると、ヒープガベージのその領域はどのように集められますか?
アレイサイズを〜32000個に増加させた理由は、スケーラビリティ(メモリ競合の減少)ですか?これはメモリ階層内の正確な原因ですか?
参考にしてクレームをサポートしてください。
ありがとうございました!
全体の実行可能なJavaプログラム:
import java.util.ArrayList;
class MultiStackJavaExperiment {
final class Foo {
int x = 0;
}
final class Worker extends Thread {
Foo[] array = new Foo[1024];
int sz;
public Worker(int _sz) {
sz = _sz;
}
public void run() {
Foo[] arr = new Foo[1024];
//Foo[] arr = array;
loop(arr);
}
public void loop(Foo[] arr) {
int i = 0;
int pos = 512;
Foo v = new Foo();
while (i < sz) {
if (i % 2 == 0) {
arr[pos] = v;
pos += 1;
} else {
pos -= 1;
v = arr[pos];
}
i++;
}
}
}
public static void main(String[] args) {
(new MultiStackJavaExperiment()).mainMethod(args);
}
int size = Integer.parseInt(System.getProperty("size"));
int par = Integer.parseInt(System.getProperty("par"));
public void mainMethod(String[] args) {
int times = 0;
if (args.length == 0) times = 1;
else times = Integer.parseInt(args[0]);
ArrayList <Long> measurements = new ArrayList <Long>();
for (int i = 0; i < times; i++) {
long start = System.currentTimeMillis();
run();
long end = System.currentTimeMillis();
long time = (end - start);
System.out.println(i + ") Running time: " + time + " ms");
measurements.add(time);
}
System.out.println(">>>");
System.out.println(">>> All running times: " + measurements);
System.out.println(">>>");
}
public void run() {
int sz = size/par;
ArrayList <Thread> threads = new ArrayList <Thread>();
for (int i = 0; i < par; i++) {
threads.add(new Worker(sz));
threads.get(i).start();
}
for (int i = 0; i < par; i++) {
try {
threads.get(i).join();
} catch (Exception e) {}
}
}
}
数字を混乱させて、あなたが探している結果を得るのは簡単です。私の答えを見ていただきありがとうございます。 –
答えをありがとう、なぜあなたはそれを削除しましたか? – axel22
私はあなたの分析と質問を読んだことがありませんでした。私は持っていなければならないし、あなたの質問に正しく答えたとは思わない。 –