2011-12-27 45 views
4

私はJavaアプリケーションをプロファイリングしていましたが、オブジェクトの割り当てが予想よりもかなり遅くなっていることを発見しました。私は小さなオブジェクトの割り当ての全体的な速度を確立しようとするために単純なベンチマークを実行しました。小さなオブジェクト(3つの浮動小数点のベクトル)を割り当てることは私のマシンで約200ナノ秒かかるようです。私は(デュアルコア)2.0 GHzプロセッサで動作しているので、これはおよそ400 CPUサイクルです。 Javaアプリケーションのプロファイリングを行っている人たちに、そのような速度が期待されるかどうかを質問したかったのです。少し残酷で珍しいようです。Javaのメモリ割り当ての典型的な速度はどれくらいですか?

int obj_addr = heap_ptr; 
heap_ptr += some_constant_size_of_object 
return obj_addr; 

....アセンブリの数行です:すべての後、私は、ヒープを圧縮し、オブジェクトを再配置することができますJavaのような言語は次のようなオブジェクト割り当て見て何かを持っていると思うだろう。ガベージコレクションに関して、私はそれが演奏されるのに十分なオブジェクトを割り当てたり破棄したりしません。オブジェクトを再利用してコードを最適化すると、処理する必要のあるオブジェクトあたり200ナノ秒ではなく、15ナノ秒/オブジェクトのオーダーのパフォーマンスが得られるため、オブジェクトを再利用するとパフォーマンスが大幅に向上します。私は本当にオブジェクトを再利用したくないです。なぜなら、それは表記法を毛づくりにしているからです(多くのメソッドは値を返す代わりにreceptacle引数を受け入れる必要があります)。

問題は、オブジェクトの割り当てには長い時間がかかりますか?あるいは、一度修正すれば、これでより良いパフォーマンスを得ることができるかもしれないマシン上で何かが間違っているかもしれませんか?小さなオブジェクトの割り当ては一般に他の人にとってどのくらいかかるのですか?典型的な価値はありますか?私はクライアントマシンを使用しており、現時点ではコンパイルフラグは使用していません。あなたのマシン上のものがより速い場合、あなたのマシンのJVMバージョンとオペレーティングシステムは何ですか?

パフォーマンスに関しては、個々のマイレージが大きく異なる可能性があることを認識していますが、上記の数字が正しい球場のように見えるかどうかを尋ねています。

+0

で再びそれを実行した場合

を(これは単なるコストを遅らせるかもしれないが)メモリ帯域幅は、おそらく支配しようとしています。 /プールを行うとGCが非効率になり、オブジェクトが分散されるとローカリティの問題が発生する可能性があります。おそらく最高のパフォーマンスを望むのは、整数オフセットを持つ1つの大きな配列(またはメモリマップされたバッファ)です。 (それはCやC++にも当てはまります。) –

答えて

2

割り当て時間の測定方法はわかりません。それはおそらく、少なくとも

intptr_t obj_addr = heap_ptr; 
heap_ptr += CONSTANT_SIZE; 
if (heap_ptr > young_region_limit) 
    call_the_garbage_collector(); 
return obj_addr; 

と同等のものをインライン化しかし、あなたはobj_addrを埋めるために持っているので、それは、それよりも複雑です。次に、JIT compilationまたはclass loadingが発生することがあります。おそらく、最初の数単語が(たとえば、クラスポインタとハッシュコードに)初期化され、いくつかの乱数生成を含むかもしれません...)、オブジェクトコンストラクタが呼び出されます。同期などが必要な場合があります。

さらに重要なことは、新しく割り当てられたオブジェクトが最も近いレベル1のキャッシュにない可能性があります。そのため、一部のキャッシュミスが発生する可能性があります。

私はJavaの専門家ではありませんが、私はあなたの措置に敬意を表しません。新しいオブジェクトを割り当てると、古いオブジェクトを再利用しようとするよりも、コードをよりクリーンで保守性の高いものにすることができます。

+0

ThreadやDirect ByteBufferのような高価なオブジェクトをプールするのは良いことです。 – ClickerMonkey

1

はい。あなたがしなければならないと思うことと実際に何をするかの違いはかなり大きいかもしれません。プーリングは面倒かもしれませんが、割り振りとガベージコレクションが実行時間のかなりの部分を占める場合、プーリングはパフォーマンス上大きな勝利です。

プールするオブジェクトは、スタックサンプルを使用して割り当てプロセスで最も頻繁に見つかるオブジェクトです。

このようなサンプルは、C++のようになります。Javaの場合、詳細は異なりますが、そのアイデアは同じです。

... blah blah system stuff ... 
MSVCRTD! 102129f9() 
MSVCRTD! 1021297f() 
operator new() line 373 + 22 bytes 
operator new() line 65 + 19 bytes 
COpReq::Handler() line 139 + 17 bytes <----- here is the line that's doing it 
doit() line 346 + 12 bytes 
main() line 367 
mainCRTStartup() line 338 + 17 bytes 
KERNEL32! 7c817077() 
           V------ and that line shows what's being allocated 
     COperation* pOp = new COperation(iNextOp++, jobid); 
4

オブジェクトの作成は、オブジェクトが小さく、GCコストがかからない場合は非常に高速です。 -verbosegc

Average object allocation took 13.0 ns. 

注意と

final int batch = 1000 * 1000; 

Double[] doubles = new Double[batch]; 
long start = System.nanoTime(); 

    for (int j = 0; j < batch; j++) 
     doubles[j] = (double) j; 

long time = System.nanoTime() - start; 
System.out.printf("Average object allocation took %.1f ns.%n", (double) time/batch); 

プリント:なしGCが発生していません。しかし、サイズを大きくすると、プログラムはGC内でメモリをコピーするのを待つ必要があります。

final int batch = 10 *1000 * 1000; 

プリント

[GC 96704K->94774K(370496K), 0.0862160 secs] 
[GC 191478K->187990K(467200K), 0.4135520 secs] 
[Full GC 187990K->187974K(618048K), 0.2339020 secs] 
Average object allocation took 78.6 ns. 

私はあなたのGCを実行しているので、あなたの割り当てが比較的遅いと思います。これを回避する方法の1つは、アプリケーションで使用できるメモリを増やすことです。私は-verbosegc -XX:NewSize=1g

Average object allocation took 9.1 ns. 
+0

実際、私は、若い世代の宇宙の不足が問題を引き起こしていることを発見しました。永続的な世代へのオブジェクトのプロモーションにはコストがかかりそうです。おそらく、その上のGCはコストがかかるので、そうです。私のベンチマークでは、私のユースケースの中には、作成されているオブジェクトが若い世代で死ぬことを考慮していませんでした。 Javaベンチマークがどのように完全に欺瞞的であるかの良い例です。 – Gravity

関連する問題