これで性能に違いはありますか
synchronized void x() {
y();
}
synchronized void y() {
}
この
synchronized void x() {
y();
}
void y() {
}
これで性能に違いはありますか
synchronized void x() {
y();
}
synchronized void y() {
}
この
synchronized void x() {
y();
}
void y() {
}
はい、JVM が への呼び出しをインライン化しない限り、またインライン化するまでは、追加のパフォーマンス コストが発生しますy()
。これは、最新の JIT コンパイラがかなり短い順序で実行します。まず、あなたが提示したy()
、クラスの外に見えるケースを考えてみましょう。この場合、JVM は開始時にチェックしy()
て、オブジェクトのモニターに入ることができることを確認する必要があります。からの呼び出しの場合、このチェックは常に成功しますがx()
、クラス外のクライアントからの呼び出しである可能性があるため、スキップすることはできません。この追加のチェックには少額の費用がかかります。
さらに、 が の場合を考えてみましょy()
うprivate
。この場合、コンパイラはまだ同期を最適化しません。次の空の の分解を参照してくださいy()
。
private synchronized void y();
flags: ACC_PRIVATE, ACC_SYNCHRONIZED
Code:
stack=0, locals=1, args_size=1
0: return
の仕様の定義によるとsynchronized
synchronized
、ブロックまたはメソッドに入るたびにオブジェクトのロック アクションが実行され、オブジェクトを離れるとロック解除アクションが実行されます。ロックカウンターがゼロになるまで、他のスレッドはそのオブジェクトのモニターを取得できません。おそらく、ある種の静的分析により、private synchronized
メソッドが他のメソッド内からのみ呼び出されることが示される可能性がありsynchronized
ますが、Java のマルチソース ファイルのサポートにより、リフレクションを無視することさえあります。これは、JVM が入力時にカウンターをインクリメントする必要があることを意味しますy()
。
メソッド呼び出し時のモニター エントリとその戻り時のモニター終了は、 monitorenterとmonitorexitが使用さ
synchronized
れたかのように、Java 仮想マシンのメソッド呼び出しと戻り命令によって暗黙的に処理されます。
@AmolSonawane は、JVM が実行時にこのコードを最適化する可能性があることを正しく指摘していますy()
。この場合、JVM が JIT 最適化の実行を決定した後、からx()
へy()
の呼び出しによって追加のパフォーマンス オーバーヘッドが発生することはありませんが、もちろんy()
、他の場所からの への直接呼び出しでは、モニターを個別に取得する必要があります。
jmh で実行したマイクロ ベンチマークの結果
Benchmark Mean Mean error Units
c.a.p.SO18996783.syncOnce 21.003 0.091 nsec/op
c.a.p.SO18996783.syncTwice 20.937 0.108 nsec/op
=>統計的な違いはありません。
生成されたアセンブリを見ると、ロックの粗化が実行され、同期y_sync
されているにもかかわらずインライン化されていることがわかります。x_sync
完全な結果:
Benchmarks:
# Running: com.assylias.performance.SO18996783.syncOnce
Iteration 1 (5000ms in 1 thread): 21.049 nsec/op
Iteration 2 (5000ms in 1 thread): 21.052 nsec/op
Iteration 3 (5000ms in 1 thread): 20.959 nsec/op
Iteration 4 (5000ms in 1 thread): 20.977 nsec/op
Iteration 5 (5000ms in 1 thread): 20.977 nsec/op
Run result "syncOnce": 21.003 ±(95%) 0.055 ±(99%) 0.091 nsec/op
Run statistics "syncOnce": min = 20.959, avg = 21.003, max = 21.052, stdev = 0.044
Run confidence intervals "syncOnce": 95% [20.948, 21.058], 99% [20.912, 21.094]
Benchmarks:
com.assylias.performance.SO18996783.syncTwice
Iteration 1 (5000ms in 1 thread): 21.006 nsec/op
Iteration 2 (5000ms in 1 thread): 20.954 nsec/op
Iteration 3 (5000ms in 1 thread): 20.953 nsec/op
Iteration 4 (5000ms in 1 thread): 20.869 nsec/op
Iteration 5 (5000ms in 1 thread): 20.903 nsec/op
Run result "syncTwice": 20.937 ±(95%) 0.065 ±(99%) 0.108 nsec/op
Run statistics "syncTwice": min = 20.869, avg = 20.937, max = 21.006, stdev = 0.052
Run confidence intervals "syncTwice": 95% [20.872, 21.002], 99% [20.829, 21.045]
テストしてみませんか!? 簡単なベンチマークを実行しました。このbenchmark()
メソッドは、ウォームアップのループで呼び出されます。これは非常に正確ではないかもしれませんが、いくつかの一貫した興味深いパターンを示しています。
public class Test {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
System.out.println("+++++++++");
benchMark();
}
}
static void benchMark() {
Test t = new Test();
long start = System.nanoTime();
for (int i = 0; i < 100; i++) {
t.x();
}
System.out.println("Double sync:" + (System.nanoTime() - start) / 1e6);
start = System.nanoTime();
for (int i = 0; i < 100; i++) {
t.x1();
}
System.out.println("Single sync:" + (System.nanoTime() - start) / 1e6);
}
synchronized void x() {
y();
}
synchronized void y() {
}
synchronized void x1() {
y1();
}
void y1() {
}
}
結果(過去 10 件)
+++++++++
Double sync:0.021686
Single sync:0.017861
+++++++++
Double sync:0.021447
Single sync:0.017929
+++++++++
Double sync:0.021608
Single sync:0.016563
+++++++++
Double sync:0.022007
Single sync:0.017681
+++++++++
Double sync:0.021454
Single sync:0.017684
+++++++++
Double sync:0.020821
Single sync:0.017776
+++++++++
Double sync:0.021107
Single sync:0.017662
+++++++++
Double sync:0.020832
Single sync:0.017982
+++++++++
Double sync:0.021001
Single sync:0.017615
+++++++++
Double sync:0.042347
Single sync:0.023859
2 番目のバリエーションは確かにわずかに速いようです。
違いはありません。スレッドは x() でロックを取得することだけを目的としているためです。x() でロックを取得したスレッドは、競合することなく y() でロックを取得できます (特定の時点でその時点に到達できるのはスレッドだけであるため)。したがって、そこに同期を配置しても効果はありません。