BT

Java 6のスレッド最適化は実際に動作しているのか? - パートII

| 作者 Jeroen Borgers フォローする 0 人のフォロワー , 翻訳者 白石 俊平 フォローする 0 人のフォロワー 投稿日 2008年9月23日. 推定読書時間: 32 分 |
この記事のパート1(参考記事)では、私たちはシングルスレッドのベンチマークを用いて、同期されたStringBufferと非同期のStringBuilderのパフォーマンスを比較しました。最初のベンチマーク結果では、他の最適化に比べてバイアスド・ロックが最もパフォーマンス向上に役立つ事が判明しました。このベンチマーク結果は、ロックの獲得が高価なオペレーションであると言う事を示唆しているようでした。しかし、そこでまとめに入る前に、私の同僚のマシンでもベンチマークを走らせて、結果の検証を行おうと決めました。ほとんどの結果は私の発見を裏付けるものでしたが、いくつかの結果は完全に異なりました。パート2では、ベンチマークの結果を検証するために用いられるテクニックについてさらに深く見ていきたいと思います。最後に、「なぜプロセッサが異なるとロックのコストも大きく異なるのか」と言う真の疑問に答えます。

ベンチマークの落とし穴

与えられた質問に答える事を目的としてベンチマーク、特にマイクロベンチマークを行うのは、とりわけ困難なものになり得ます。しばしばベンチマークは、あなたが測定しようとするものとは完全に異なるもので終わってしまう事があります。問題になっている効果を測定しようとしているときでも、他の影響によって測定結果が狂わされてしまいます。不正なベンチマーク結果と言う落とし穴を避ける  ためには、このエクササイズの最初にベンチマークを丹念に調べて、他の人にレビューしてもらう必要があったのは明らかです。以下の節で述べるように、ベンチマークをレビューしてもらうだけではなく、数多くのツールやテクニックを用いて、自力で結果を検証する努力も行いました。

結果の統計的処理

コンピュータが実行する処理のほとんどは、一定の処理時間を持ちます。私の経験では、処理時間が不定な処理であっても、ほとんどの条件下においてほぼ一定の時間内に完了します。コンピューティングが持つこうした特性により、私たちはツールを用いて、物事が期待通り進んでいない事を知る事が出来るのです。そうしたツールは統計的処理を行いますが、測定結果にはばらつきがあります。つまり、単純な平均を上回る結果が出た事の理由について、"必死に"説明する事が求められている訳ではないと言う事です。その理由は次のようなものでしょう; 私が実行した固定数のCPU命令が、相対的に一定の時間枠で完了しなかったのであれば、私の測定に対して何らかの外部的な影響があった事を示すものだ、と。私が知っているのは、ベンチマーク結果に大きなばらつきが見られたときは、外部の影響を見つけ出し、取り扱わねばならないと言う事です。

こうしたばらつきの影響は、マイクロベンチマークでは誇張されてしまいます。しかし、より大きなベンチマークではそうではありません。より大きなベンチマークでは、アプリケーションの測定される側面がお互いに干渉し合い、いくらかのばらつきを生成します。しかし再び言いますが、測定に影響する干渉の度合いとして、ばらつきは私たちに良い情報を提供してくれるのです。ある一定の負荷においては、(むしろ)私はばらつきを期待します; まあ、あまり変化する訳ではないのですが。ほとんどの結果よりも大きい/小さいと言うようなばらつきが見えると、それはベンチマークがきちんと分離された環境にない、もしくは設定されていない事を示している、と言う風に利用する事が出来る訳です。「同じ測定を行っているのに違いが生じる」と言う場合は、完全なベンチマークとマイクロベンチマークの間の差異を示している、として扱えば良いでしょう。

 

最後に一点。あなたが測定していると考えている「何か」は、依然としてあなたが測定しているはずのものではありません。それは「あなたが測定中の何か」でしかありません。究極の問題に対しては、測定とは「おおむね正しいようだ」としか言えないものなのです。

メソッドキャッシュのウォーミングアップ

ベンチマークに干渉する活動の一つに、コードのJITコンパイルがあります。信頼できるエージェントであるHotspotは、最適化を施すチャンスを探して、アプリケーションを常にプロファイリングしています。Hotspotがチャンスを見つけると、問題のコードセクションを再コンパイルするようJITコンパイラに要請します。On Stack Replacement(OSR)として知られるテクニックを利用し、新しいコードを実行するよう切り替えます。OSRの実行により、あらゆる種類の波及効果が起こります。実行中のスレッドを一時停止する必要も生じます。もちろん、こうした活動のすべてが私たちのベンチマークに干渉します。その干渉は、測定結果をゆがめる種類のものです。コードがJITコンパイルされた事を知るのに役立ち、かつ私たちが自由に利用できるツールが二つあります。一つ目は(もちろん)ばらつきであり、二つ目はフラグ-XX:-PrintCompilcationです。とても幸いな事に、ベンチマークのはじめにおいて全てのコードが公平にJITコンパイルされないとしても、単にそれを例外的な起動時処理として扱ってしまう事が出来ます。私たちが行わなければいけないのは、全てのコードがJITコンパイルされるまでベンチマークを走らせ、それから計測を開始する事です。このウォームアップフェーズは、"メソッドキャッシュのウォーミングアップ"としてよく知られています。

ほとんどのJVMは、インタープリタとネイティブモードを同時に実行します。これはミックスモード実行として知られています。時間が経つと、インタープリタによって実行されていたコードは、HotspotとJITコンパイルによりプロファイル情報を用いてネイティブコードへと変換されます。どの最適化が実行されるべきかをHotspotが理解するための手助けをするには、メソッド呼び出しと分岐をサンプリングする必要があります。一度それが特定のしきい値に達すると、ネイティブコードを生成するためにJITコンパイルが発生します。そのしきい値は、-XX:CompileThresholdフラグを用いてセットする事が出来ます。例えば-XXCompileThreshold=10000とすると、コードが10,000回実行されたあとにHotspotはコンパイルを行います。

ヒープ管理

次に考慮しなくてはならないアクティビティはガベージコレクション、より正式にはヒープ管理として知られているものです。メモリ管理のアクティビティはいくつか存在し、通常どんなアプリケーションを実行している間にも行われます。これらのアクティビティは、ヒープスペースのリサイズ、もはや使われていないメモリの再利用、ある場所から他の場所へのデータ移動、等を含みます。こうしたメンテナンス活動により、アプリケーションとJVMが競合状態になる必要が生じます。問題となるのは: ベンチマークは、メンテナンスやガベージコレクションに必要とされる時間を含むべきかどうか?です。この答えは実際には、答えを出そうとしている質問の種類に依存します。今回の例では、私はロックの獲得コストにのみ興味がありました。つまり、私はガベージコレクションにかかる時間を除いて計測しなくてはならないと言う事を意味します。もう一度言いますが、数値のばらつきはベンチマークに何か不具合がある事を伝えます。通常疑われるのはガベージコレクションです。「GCが有罪である」と言う判決を下すには、-verbose:gcフラグを用いてGCのロギングを有効にするのが最も良い方法です。

私のベンチマークでは多くのString、StringBuffer、StringBuilderを取り扱っています。全体としては、このベンチマークを実行するごとにだいたい4000万オブジェクトを生成します。このレベルでオブジェクトを激しく生成していたら、ガベージコレクションが問題になるのは間違いありませんでした。私は二つのテクニックを使いました。最初に私はEdenヒープ空間のサイズを増加させ、ベンチマーク内での繰り返し処理が行われている間に、ガベージコレクションが行われるのを妨げました。これが、私の用いたコマンドラインです。:

>java -server -XX:+EliminateLocks -XX:+UseBiasedLocking 
-verbose:gc -XX:NewSize=1500m -XX:SurvivorRatio=200000 LockTest

そして私はリスト1のようなコードを入れ込み、次の繰り返し処理のためのヒープの準備を行いました。

System.gc();
Thread.sleep(1000);


リスト1. GCを行ってから、少しの間スリープする

スリープの目的は、ガベージコレクタが他のスレッドを解放してから終了できるようにする事です。注意すべきは、いくつかのCPUは何の活動も行われていない場合にクロックスピードをステップダウンしてしまう事です。従ってスリープを行うと、CPUクロックの回転が元に戻るまでの間に遅延が発生してしまいます。もしあなたのプロセッサがこの機能をサポートしているなら、ベンチマークの間はファームウェアレベルで節電機能をオフにしておく必要があります。

上のフラグをセットする事が、実行中のGCを妨ぐと言う訳ではありません。各テストケースで一度だけGCを実行すると言う事を示しています。ポーズする各時間は十分に小さく、結果に対しては全体として小さな影響しか及ぼしません。私たちの目的には十分役に立ちます。

バイアスド・ロックの遅延

ベンチマーク結果にはっきりと影響する効果を持つものがもう一つ存在します。ほとんどの最適化は非常に早い段階で適用されるのに対し、バイアスド・ロックは 3, 4秒経過したあとでなければ適用されません。その理由はよくわかりません。危うくベンチマーク結果が使い物にならなくなるところでしたが、ばらつきがまたもや重要な役割を果たし、こうした問題が発生していると言う事を突き止める事が出来ました。フラグ-XX:+TraceBiasedLockingは、こうした問題を追跡するのに役立ちます。ウォームアップの時間は、この遅延を考慮して調節されました。

その他のHotspotによる最適化

Hotspotは、一度最適化を行ったあとでもプロファイリングを辞めません。その代わりに、更なる最適化を行う機会を探してプロファイルを続けます。ロックに関しては、単純に適用する事の出来ない最適化が数多く存在します。なぜならJavaメモリモデルで述べられている仕様を破ってしまうからです。しかしロックがJITコンパイルされたなら、そうした制限はすぐになくなります。このシングルスレッドのベンチマークの場合、Hotspotに取ってはロックを除去するのはきわめて安全です。(さらに)メソッドのインライン化、ループ内の不変式をループ外に移動する、デッドコードの除去といった他の最適化を適用するチャンスも残っているのです。

このサイドバーにあるコードを見てみましょう。AとBはどちらも不変です。2番目のリストに示したように、計算式をループの外に出し、三つ目の変数を作り出す事で、計算が繰り返し行われるのを避ける事が出来ます。伝統的には、こうした作業を行うのはプログラマの責任でした。しかしHotspotはループの不変式を認識して、それをループ外に移動することが十分に可能です。したがって、私たちがリスト2のようなコードを書いたとしても、リスト3に示したようなコードが実行されるのです。
  int A = 1;
int B = 2;
int sum = 0;
for (int i = 0; i < someThing; i++) sum += A + B;

リスト2. 不変式を含むループ

  int A = 1;
int B = 2;
int sum = 0;
int invariant = A + B;
for (int i = 0; i < someThing; i++) sum += invariant;

リスト3. 不変式を外に出したループ

こうした最適化を許容すべきか、それとも防ぐための努力を行うべきかは議論の余地があるところです。少なくとも、こうした最適化が行われる事を知っておく必要があります。デッドコードの除去は、私たちが明らかに無効化すべき最適化の一つです。さもないと、私たちのベンチマークは何も測定しない事になってしまいます!concatBufferとconcatBuilderの結果が利用されていない、と判断する事はHotspotにとって可能です。これらの操作は何の副作用も持っていないように見えます。従って、コードを実行すべき点が全く存在しないのです。一度コードが"デッドコードである"と判断されると、それを除去するようJITに働きかけます。幸いにも私のベンチマークはHotspotを混乱させ、こうした最適化が可能だと判断されていません...今のところは。

また、もしロックがあるとメソッドがインライン化されず、ロックがなければ行われる、と言うのであれば、それも困り者です。メソッドコールに必要な余分のコストをベンチマーク結果に含めないようにしなくてはなりません。Hotspotをだますために今のところ使えるテクニックの一つとしてはインターフェースを作成する事です(リスト4)

public interfaceConcat {
String concatBuffer(String s1, String s2, String s3);
String concatBuilder(String s1, String s2, String s3);

public
class LockTest implements Concat {

...}

リスト4. メソッドのインライン化を妨げるためにインターフェースを使用する

インライン化を防ぐための他の方法は、コマンドラインオプション-XX:-Inlineを使用する事です。このベンチマークの測定結果においては、メソッドのインライン化による差異が生じていない事も確認してあります。

トレース結果

最後に、以下に示したフラグを用いたときに出力された結果をお見せしたいと思います。

>java -server -XX:+DoEscapeAnalysis -XX:+PrintCompilation 
-XX:+EliminateLocks -XX:+UseBiasedLocking
-XX:+TraceBiasedLocking LockTest


図1. ベンチマークのトレース出力

デフォルトでは、JVMは12個のスレッドを開始しています; メインスレッド、参照ハンドラ、ファイナライズ、アタッチリスナなどです。グレーで示された最初のセクションは、これらのスレッドオブジェクトがどう配置されたかを報告しており、バイアスド・ロックとともに働きます(全てのアドレスが00で終わっている事に注目)。あなたはこのセクションを無視して構いません。黄色で示された次のセクションでは、メソッドのコンパイルに関する情報が含まれています。5行目と12行目を見ると、追加の"s"でマークされているのを見る事が出来るでしょう。表1によると、それらのメソッドはsynchronizedだと言う事がわかります。"%"を含む行は、OSR(On Stack Replacement)を使用しています。赤でマークされた行はバイアスド・ロックが有効になった箇所です。最後の水色のエリアは、ベンチマークの時間計測が開始された箇所です。この出力結果から、全てのコンパイルはベンチマークが開始されるまでに実行されていると言う事が読み取れるでしょう。これは、ウォームアップ期間が十分に長い事を確かなものとしています。ロギングの出力結果についてさらに詳しく知りたい方は、こちらのページ(リンク)とこちらの記事(リンク)を参照すると良いでしょう。 

表1. コンパイルコード

シングルコアのCPUにおける結果

私の同僚の多くはIntel Core 2 Duoプロセッサを使用しているのですが、古い単一プロセッサマシンを使っている者もわずかながらいました。こうした古いマシンにおける StringBufferのベンチマークは、StringBuilderの実装によって作り出されたものとほとんど変わりませんでした。その差異は(ロック以外の)いろんな要因で説明できますので、他のテストを行って、可能な限り多くの可能性を除外しなくてはなりませんでした。結局、ベンチマークを再実行するためのベストな選択肢は、BIOSで私のマシンのCore 2 Duoのコアを一つ無効にすることでした。図2に示したのが、それらの実行結果です。

図2. シングルコアのパフォーマンス

マルチコアで実行した場合のように、基準値は、3つの最適化全てを無効にして取得しました。StringBuilderのパフォーマンスはやはり均一なスループットでした。さらに興味深い事に、StringBuilderよりもわずかに遅いものの、StringBufferのパフォーマンスは、マルチコアプラットフォーム上で動作させたときよりもStringBuilderに近いものでした。このテストは、「このベンチマークは実際には何を測定しているのか」をはっきりさせるための情報を提供してくれています。

マルチコアの世界では、スレッド間でデータを共有すると言うのが全く違うものになります。あらゆる近代的なCPUはローカルのメモリキャッシュを活用して、命令やデータをメモリからフェッチするための遅延を最小化しなくてはなりません。私たちがロックを使用すると、実行パスに対してメモリバリアを挿入する事になります。メモリバリアはCPUに対し、最新の値をフェッチするためには、他のCPU全てと協調しなくてはならない、と言う事を伝えるシグナルです。このようにする事で、CPUはお互いにコミュニケーションを行います。これにより各プロセッサは、現在実行中のアプリケーションスレッドを一時停止する事になります。どれだけの間停止しているかは、CPUのメモリモデルが持つ機能次第です。メモリモデルが保守的であればあるほど、よりスレッドセーフになりますが、全てのコアの間でより長い時間協調している必要があります。Core 2 Duoでは二番目のコアによって、固定的な作業量におけるベンチマークの実行時間が3731 msから6574 msへと増加されました。176%の増加です。アプリケーションの全体的なパフォーマンスを向上させるのに、Hotspotが何らかの助けになるのは明らかです。

エスケープ解析は本当に動作しているのか?

明らかに適用可能なのにも関わらず、有効に働いていない最適化の一つがロックの除去でした。つまり、ロックの除去が実装されたのはほんの最近ですし、それが依存しているエスケープ解析についても、プロファイル技術自体がほんの最近実装されたばかりだと言う事です。とはいうものの、これらのテクノロジーは限られた数のケースにおいてのみ動作すると宣伝されています。例えばエスケープ解析は、ローカルで宣言されたロックを用いた同期ブロック内でローカル変数をインクリメントする、というような単純なループに対して動作します [http://blog.nirav.name/2007_02_01_archive.html] 。また、Scimark2ベンチマークのMont Carloテストで動作する事が約束されています([http://math.nist.gov/scimark2/index.html] を見てください)。

エスケープ解析をテストに持ち込む

では、これらのケースではエスケープ解析が動作するのに、なぜ私たちのベンチマークでは動作しないのでしょうか?私はStringBufferと StringBuilder内の必要なメソッドをインライン化して、ベンチマークにかかる時間を削減するよう努めました。また私はコードを修正して、エスケープ解析が無理矢理動作するよう試みました。私はいくつかの箇所でロックが除去されるであろう事、パフォーマンスが劇的に向上するであろう事を期待していました。このベンチマークを克服するのが混乱と不平不満の両方をもたらした、と言うのは真実ではありません。私はロック除去が動いたと思ったのに、様々な理由により、全く動かないバージョンへと戻すため、エディタ内でCtrl-Zを大量に使用するはめになりました。時々、ロックの除去ははっきりした理由無しにいきなり動き出したりするのでした。

最終的に私は、ロック除去とロックされたオブジェクトのデータサイズの間に、何らかの関係が存在する事を認識するようになりました。リスト2のコードを実行すれば、それがわかるでしょう。以下を見ればお分かりの通り、DoEscapeAnalysysフラグは効果がなく、実行時間には何の差異もありません。

 >java -server -XX:-DoEscapeAnalysis EATest
thread unsafe: 941 ms.
thread safe: 1960 ms.
Thread safety overhead: 208%

>java -server -XX:+DoEscapeAnalysis EATest
thread unsafe: 941 ms.
thread safe: 1966 ms.
Thread safety overhead: 208%

次に示す2回の実行では、ThreadSafeObjectクラスの未使用フィールドであるindexを取り除きました。お分かりの通り、エスケープ解析を有効にすると、パフォーマンスの向上がはっきりと生じています。

 >java -server -XX:-DoEscapeAnalysis EATest
thread unsafe: 934 ms.
thread safe: 1962 ms.
Thread safety overhead: 210%

>java -server -XX:+DoEscapeAnalysis EATest
thread unsafe: 933 ms.
thread safe: 1119 ms.
Thread safety overhead: 119%

エスケープ解析は、WindowsとLinuxのどちらでも効果がありました。しかしMac OS Xでは、余分な未使用変数を持つ事による影響はなく、どちらのバージョンのベンチマークでも結果は120%となりました。これにより、Mac OS Xではエスケープ解析の効果とそれが起こる状況の範囲が少し広いのだ、と信じられるに至りました。私の推測では、非常に保守的な実装が行われており、ロックオブジェクトのデータサイズやOS固有の機能のような様々な条件に基づき、初期では無効になっているのでしょう。

まとめ

Hotspot が持つロック最適化の効果を試すためこのエクササイズを始めたとき、私は2,3時間費やせば役に立つブログエントリが書けるだろう、と踏んでいました。しかしあらゆるベンチマークと同じく、結果を検証して解釈を行うプロセスに、延々と何週間もかける事になりました。ベンチマークを検証して、その人なりの解釈を提供することに時間を多く割いてくれるような、他のエキスパートと協力する必要もありました。こうした全ての作業が終わったあとでも、どの最適化が働き、どれが働いていないのかについて、多くを語るのは依然として不可能です。この記事は具体的な結果を引用してはいますが、それらは私のハードウェアに固有のものです。あなたのシステムでも同じような結果が確認できるだろう、と仮定する事だけは出来ます。また、私は小さくて程々のマイクロベンチマークを想定したところからはじめましたが、「ベンチマークにおかしなところがないのにHotspotが想定外の最適化を行っていた」と言う事を私自身、そしてコードをレビューしてくれた人々を納得させるため、結局多くの調整を行うはめになりました。要するに、私が最初に期待していたものよりも随分と難しいものになってしまった、と言う事です。

もしあなたがマルチスレッドのアプリケーションをマルチコアのマシン上で走らせる必要があり、パフォーマンスが気になっているのであれば、使用している JDKのバージョンにおける最新のリビジョンへと、継続的にアップグレードしていく必要があるのは確かです。最適化の多く(全てではありません)は、以前のバージョンにおける最新リビジョンに対してバックポートされているからです。全てのスレッド最適化を有効にする必要も生じるでしょう。6.0では、それらはデフォルトで有効になっています。しかし5.0においては、それらをコマンドラインで明示的に指定する必要があります。もしあなたがシングルスレッドアプリケーションをマルチコアマシン上で動かしているなら、一つ目のコア以外の全てをスイッチオフする事で、あなたのアプリケーションをより早く動かす事が出来るかも知れません。

より低いレベルでは、ロックのオーバーヘッドはデュアルコアプロセッサにおいてよりも、シングルコアのほうが低くなります。Intelコアの協調動作はメモリバリアのセマンティクスですが、私のシステムにおいては、はっきりとしたオーバーヘッドがありました。コアの一つを動作させずに実行した、ベンチマークのパフォーマンスがその証拠です。このオーバーヘッドを減らすため、スレッド最適化が必要なのは明白です。幸運にも、ロックの疎粒度化、そしてとりわけバイアスド・ロックは、はっきりとした効果を持っていると言う事がベンチマークのパフォーマンスによって示されています。私はロック除去とエスケープ解析の組み合わせが、もっと多大な効果をもたらすと期待していました。このテクノロジーは動作しますが、非常に限られたケースにおいてのみです。とは言うものの、エスケープ解析は依然として初期段階であり、この複雑なツールが成熟するのには時間が必要でしょう。

結論としては、最も良いベンチマークとは、あなたのアプリケーションをあなたのハードウェア上で動かす事です。あなたのマルチスレッドアプリケーションのパフォーマンスが期待していたほどのものではないという場合に、この記事がいくつかの見解をもたらす事が出来れば幸いです。

Jeroen Borgersについて

 

Jeroen Borgersは、ITアーキテクト集団Xebiaのシニアコンサルタントです。Xebiaは、エンタープライズJavaとアジャイル開発に特化した、国際的なITコンサルタント会社兼プロジェクト組織です。Jeroen は、エンタープライズJavaのパフォーマンス問題で顧客を助け、Javaパフォーマンスチューニングコースの講師を務めています。彼は1996年から開発者として、アーキテクトとして、チームリーダーとして、品質責任者として、助言者として、監査役として、パフォーマンステスターとチューニング担当者として、いくつもの業種で様々なJavaプロジェクトに従事してきました。彼は2005年から、もっぱらパフォーマンスの仕事に従事しています。

謝辞

この記事は、いろんな他の人の助けを得て完成する事が出来ました。スペシャルサンクス:

Cliff Click博士, SunのサーバーVMにおけるかつてのリードアーキテクトであり、現在Azul Systemsで働いています; 分析の手伝いと、貴重な情報源を教えてくれました。

Kirk Pepperdine, Javaパフォーマンスの権威です; 貢献を行ったり、広範囲の編集作業を行ってくれたことに加え、その過程においても手助けしてくれました。

David Dagastine, SunのJVMバフォーマンスチームリーダーです。説明を行ってくれただけではなく、私を正しい方向に導いてくれました。

Xebiaの同僚たちは、ベンチマークの実行を手伝ってくれました。

Resources

実践的なJava並行処理については、Brian Goetzの記事などがあります。

Java theory and practice: Synchronization optimizations in Mustang (リンク)

Did escape analysis escape from Java 6 (リンク)

Dave Dice's Weblog (リンク)

Java SE 6 Performance White Paper (リンク)

Listing 1.


  public class LockTest {

        private static final int MAX = 20000000; // 20 million


        public static void main(String[] args) throws InterruptedException {

// warm up the method cache

for (int i = 0; i < MAX; i++) {

                        concatBuffer("Josh", "James", "Duke");

                        concatBuilder("Josh", "James", "Duke");

}

               System.gc();

               Thread.sleep(1000);


                                        long start = System.currentTimeMillis();

               for (int i = 0; i < MAX; i++) {

                        concatBuffer("Josh", "James", "Duke");

               }

               long bufferCost = System.currentTimeMillis() - start;

               System.out.println("StringBuffer: " + bufferCost + " ms.");


               System.gc();

               Thread.sleep(1000);


start = System.currentTimeMillis();

for (int i = 0; i < MAX; i++) {

concatBuilder("Josh", "James", "Duke");

}

 

long builderCost = System.currentTimeMillis() - start;

 

System.out.println("StringBuilder: " + builderCost + " ms.");

System.out.println("Thread safety overhead of StringBuffer: "

 

+ ((bufferCost * 10000 / (builderCost * 100)) - 100) + "%\n");

 


}


 

public static String concatBuffer(String s1, String s2, String s3) {

 

            StringBuffer sb = new StringBuffer();

            sb.append(s1);

            sb.append(s2);

            sb.append(s3);

            return sb.toString();

 }


 public static String concatBuilder(String s1, String s2, String s3) {

 

            StringBuilder sb = new StringBuilder();

            sb.append(s1);

            sb.append(s2);

            sb.append(s3);

            return sb.toString();

 

}

 


}



Listing 2.

public class EATest {


    private static final int MAX = 200000000; // 200 million


    public static final void main(String[] args) throws InterruptedException {

           // warm up the method cache

           sumThreadUnsafe();

           sumThreadSafe();

           sumThreadUnsafe();

           sumThreadSafe();

           System.out.println("Starting test");


           long start;


           start = System.currentTimeMillis();

           sumThreadUnsafe();

           long unsafeCost = System.currentTimeMillis() - start;

           System.out.println(" thread unsafe: " + unsafeCost + " ms.");


           start = System.currentTimeMillis();

           sumThreadSafe();

           long safeCost = System.currentTimeMillis() - start;

           System.out.println(" thread safe: " + safeCost + " ms.");

   System.out.println("Thread safety overhead: "

 

                    + ((safeCost * 10000 / (unsafeCost * 100)) - 100) + "%\n");


    }


    public static int sumThreadSafe() {

           String[] names = new String[] { "Josh", "James", "Duke", "B" };

             ThreadSafeObject ts = new ThreadSafeObject();

             int sum = 0;

           for (int i = 0; i < MAX; i++) {

           sum += ts.test(names[i % 4]);

           }

           return sum;

    }


    public static int sumThreadUnsafe() {

           String[] names = new String[] { "Josh", "James", "Duke", "B" };

           ThreadUnsafeObject tus = new ThreadUnsafeObject();

           int sum = 0;

           for (int i = 0; i < MAX; i++) {

                   sum += tus.test(names[i % 4]);

           }

           return sum;

    }


}


final class ThreadUnsafeObject {

      // private int index = 0;

      private int count = 0;


      private char[] value = new char[1];


      public int test(String str) {

             value[0] = str.charAt(0);

             count = str.length();

             return count;

       }

}


final class ThreadSafeObject {

      private int index = 0; // remove this line, or just the '= 0' and it will go faster!!!

      private int count = 0;


      private char[] value = new char[1];


      public synchronized int test(String str) {

             value[0] = str.charAt(0);

             count = str.length();

             return count;

    }

}


原文はこちらです:http://www.infoq.com/articles/java-threading-optimizations-p2
(このArticleは2008年6月27日に原文が掲載されました)

この記事に星をつける

おすすめ度
スタイル

こんにちは

コメントするには InfoQアカウントの登録 または が必要です。InfoQ に登録するとさまざまなことができます。

アカウント登録をしてInfoQをお楽しみください。

あなたの意見をお聞かせください。

HTML: a,b,br,blockquote,i,li,pre,u,ul,p

このスレッドのメッセージについてEmailでリプライする
コミュニティコメント

HTML: a,b,br,blockquote,i,li,pre,u,ul,p

このスレッドのメッセージについてEmailでリプライする

HTML: a,b,br,blockquote,i,li,pre,u,ul,p

このスレッドのメッセージについてEmailでリプライする

ディスカッション

InfoQにログインし新機能を利用する


パスワードを忘れた方はこちらへ

Follow

お気に入りのトピックや著者をフォローする

業界やサイト内で一番重要な見出しを閲覧する

Like

より多いシグナル、より少ないノイズ

お気に入りのトピックと著者を選択して自分のフィードを作る

Notifications

最新情報をすぐ手に入れるようにしよう

通知設定をして、お気に入りコンテンツを見逃さないようにしよう!

BT