先月の Java 批判に対する酷評について、こちらの java.net blog でいくつかご意見をいただきました。 普段のパフォーマンスという話題から外れているのは認めざるを得ませんが、こういうことを書かずにいられないくらい、自分は非常に Java に肩入れしているということなのでしょう。たぶん。
ここからはパフォーマンスに話題を戻しますが、さきほどの多少 Java 批判とも関連があります。 なぜなら Java の主たるライバルとして何度も浮かび上がるものの一つに .NET があるからです。 個人的には、.NET に惹かれません。 すでにいくつか比較も出てきています。 興奮しつつ "Mono" に言及する意見もいくつかありましたが、私の見る限り、仮に Mono がマイクロソフトのビジネスに食い込むようなことがあったとしたら、間違いなくマイクロソフトは直ちに Mono の息の根を止めることでしょう。特許を持っているのはマイクロソフトだからです。 それを除くとして、両者を比べるとどうでしょうか? 数ヶ月前に Javva The Hutt が、これまで我々が見た中で最も参考になる両者の 機能比較 を行っています。
パフォーマンスに関しては、 CLR 設計者の一人である Rico Mariani氏 の、.NET パフォーマンスに関するblog に、.NET は Java のパフォーマンスにまだまだ及ばない、と述べています。 性能改善のために「ポインタを減らし、仮想メソッドは少なく」するというのは、関数指向のプログラミングをしろと言っているのとあまり変わりません。 Javaではわずかなコストで多態性を利用できるし、HotSpot はまさにオブジェクト指向システムの最適化のためにあります。 まあともかく、どちらにも特徴はあります。 Java と .NET の2者に投入されている物量の大きさを考えれば、最終的にはあまり大きな差は出てこないでしょう。 かたやクロスプラットフォームのサポートと、標準化された API を持ち、もう一方はプロプライエタリな縛りがあることをのぞけば、ですが。 lock-in.
最後に一言。この件についての Malcolm Davis の Blog 記事をごらんになってください。 要点を述べると、( Ant と JUnit の .NET 移植版として)NAnt と NUnit があるにもかかわらず、マイクロソフトは同等のツールを自分で作ってサポートしていくというのです。 まさに彼の指摘するとおり、「仮にも Java の IDE が、『うちは自前のツールセットを作るから JUnit や Ant はサポートしません』などという状況を想像できますか?」
今回のニュースレターでも、いつものとおり数々の記事とニュース、おなじみのセクションをご用意しています。 Javva The Hutt はセキュリティに関する大発見をしたようです。また、日記では読者から届いたいくつかの質問に答えています。 "profiler" 氏からは パフォーマンスチューニング漫画の新作 が届いています。 今月の質問 は古典的なチューニングパラメータについてです。 そして今月も 多数のパフォーマンス tips を見やすくリストアップしています。
Javaパフォーマンスチューニング関連ニュース
私たちのコンピュータにやってこようとしている、Javaの最新最大のバージョンについて考えるべき時が近づいています。 ほかでもないTiger、別名JDK1.5、あるいはJava5.0です。 いまや私は1.2、1.3、1.4の進化と、Java2という概念をすべて手に入れられるわけです。 しかし理解に苦しむのは、なぜSunが3と4を飛ばしてJava5に行ってしまったのかということです。 理由は1.5で私たちが目にすることになる莫大な変化に関係があるのだろう、とは思いますが。 おかしなことに、多くの人々は今までそのことに注意を向けてきませんでした。 もちろん、ジェネリックや自動boxingくらいは、もうほとんどの方が耳にしているでしょう。 言語の核心的な部分におけるこうした変化が、私たちの開発能力にどれだけの影響をもたらすか考えたことはありますか? こうした機構のパフォーマンスに対するインパクトはどうでしょうか?
場合によっては、これらの変化は実際にはすべてコンパイラの中に閉じた話なので、パフォーマンスへの影響はないはずです。 たとえば、ジェネリックが関係するのはほとんどが型の安全性とキャストです。 その文脈では、処理はコンパイル時に行われるので実行性能への影響はないはずですが……実際にはどうでしょう? おそらく、コンパイラがこの新しい構文をどれだけ効率よくバイトコードに変換できるかどうか、にかかってくるでしょう。 それに対して、自動boxingはすでに性能低下につながることが立証されています。 これは私にとって驚きではありませんでした。 私は一年以上前に自動boxingの導入に反対する意見をJava Developer Journalで発表しました。 そこで述べたとおり、自動boxingの問題はプリミティブ値をラップしたり取り出したりする際に、大量のゴミが発生することです。 あまり笑えない話ですが、実は自動boxingを不要なものにし、かつ何のデメリットもない完璧な解決策が存在するのです。 残念ながら今までその解は見過ごされてきたか、あるいは無視されてきました。 私に言わせれば無視したのでしょう。自動boxingを提唱した人々は、SmallTalkにはすでにプリミティブの扱いに関する完璧な解決方法があることを知らないはずがないからです。 SmallTalkにはイミディエートオブジェクトと呼ばれる構造体があります。 イミディエートオブジェクトと通常のオブジェクトの違いは、値がヒープではなくてオブジェクト指向ポインタ(OOP)に格納されている点にあります。 大事なのは、これらすべてがVMによって処理されるので、プログラマはその違いを意識することは一切なく、イミディエートオブジェクトと通常のオブジェクトは全く同じに見えるということです。
1.5(5.0でもなんでいいですが)のリリースが近づいている今、その安定性も増していますし、これらの新機能がベンチマークでどう振る舞うか見てみたいという気持ちも強くなっています。 いつものとおり、VM全般の性能は1.4よりも大幅に良くなっていることでしょう。 1.4は1.3に比べて大きく改善されていましたし、同様に、1.3も1.2から大きく進歩していました。 当コラムでマイクロベンチマークを行う予定ですので、どうぞお見のがしなく。 続いては今月のディスカッショングループの動向をご覧ください。
今月のまとめはJava Ranch (www.javaranch.com)から始めます。 バーでの最初の質問は、納品するコードにリフレクションを使ってはいけないと言う顧客要件についての相談です。 一般的にはリフレクションは遅いと考えられていて、実際にも直接メソッドを呼び出すよりは遅いのです。 そこで、いったいどれだけ遅いのか、実のところ問題にするほどのことなのか、という質問でした。
リフレクションは直接メソッドを呼び出すよりもはるかに多くの手順が必要になるため、普通ならその使用には消極的になるでしょう。 まずメソッドを見つけなくてはいけないので、メソッドのシグニチャを再作成します。 メソッドを見つけたら、引数を配列に詰め込み、メソッドを直接呼び出します。 直接呼び出しでは、メソッドを直接呼ぶという最後の1手順を実行するだけです。 余分な手順がある以上、よけいな時間がかかるに決まっています。 しかしこれが1回しか実行されないのであれば、問題になるでしょうか? もしアプリケーションの性能要件が満たされているなら、問題になるでしょうか? リフレクションを使用しないと言う顧客要件があったとしても、無視して良いものでしょうか? 答えはどれも「否」です。 スレッド中では、リフレクションのパフォーマンスに関する記事へのリンクの投稿もありました。 その文書への評価が行われる中で、マイクロベンチマークに誤りがあり、結果に影響を及ぼすおそれがあることが明らかになりました。 当コラムでは繰り返し述べていますが、マイクロベンチマークを使う際は気をつけるべし、という教訓でした。
別の議論では、あるアルゴリズムのオメガ値(Big O)を計算する方法についての質問がありました。 これはきわめて学術的な問題で、あまり実用的な用途はありませんが、最悪のシナリオにおけるアルゴリズムの強度を表現するために使える、という点で重要な意味をもちます。 問題のアルゴリズムは、N個のランダムかつ重複しない数のリストを作成する、というものでした。 乱数生成器がN個の重複しない数を生成できない、という本当にまずい事態をのぞけば、リストを一つ一つ調べて重複がないか確認しなくてはいけないというのが考慮すべき最悪のケースでしょう。 そしてその数字をリストに追記するというわけです。 この最悪のケースでは、(乱数生成器を除けば)アルゴリズムはO(N^2)となります。
強力なアルゴリズムの話としては、別のスレッドでJavaによるサイン・コサイン算出の性能に関する質問が出ました。 ここではベキ級数がサイン・コサインの値を近似するのに使える、という良い指摘がありました。 これらのベキ級数は32bit数値なら6回ほどのイテレーションで収束します。 これがJavaの標準ルーチンよりも速いということには驚きますが、Javaゲームプログラマ諸氏はそんなにJavaの算術ルーチンを回避するのに時間を費やしているのでしょうか?
Javaの起動にかかる時間について、良いニュースです。 JDK1.5では1.4に比べて起動時間が大幅に早くなったと報じられています。 Windowsに前もってVMを起動させておくことで、体感上の起動時間を短くできないか、という話から始まった議論の中で、この情報がぽろりと出てきたのでした。 実際には、それはWindowsの起動時間の中にJVMの起動時間を潜り込ませているだけで、歓迎はされないだろう、という指摘を受けて、スレッドそのものは即死の憂き目にあうのですが。 この指摘については確かに私も同意見です。Windowsの起動時間はもう十分に長すぎるので、この上さらに遅くなるという話には関心を持てません。
java gamingのホームページに、Jeff Kesselmanが長い休暇を終えて戻ってきた、というアナウンスがありました! みなさん、Kesselman専用の通訳を用意しておきましょう。(Jeffはタイピングの壮絶な雑さで悪名を馳せています)彼の投稿の中にエンコードされた情報は、時間をかけて解読するに値するものです。
フレーム生成部はあらゆるゲームアプリケーションの核心です。そのため、このトピックについては確実に多くの議論が起きています。 素晴らしいことに、そのスレッドの中のある投稿で、この図が提供されました。(http://misfit.wox.org/jvm-options-list.html) そして、ガベージコレクション、ガベージコレクションによる処理停止、オブジェクトプーリングに関する議論が始まりました。 オブジェクトプーリングについては、プーリングを深く使いこなしている勢力と、オブジェクトが死んで回収されるに任せる勢力(GCとの関連がここにあります)という2つの勢力が生まれました。 オブジェクトの一生をVMに任せれば、複雑なコードを書かずにすみます。 オブジェクトプーリングを使うと複雑さは増しますが、GCの負荷を減らすことにもなります。 プールの管理にはほぼ確実に同期処理が必要になり、パフォーマンス悪化の原因になります。 それでもなお、ある参加者のゲームではプールを使うことで、実際に感じられるほど性能が向上した、とプールを使う側は主張します。 真実を明らかにしましょう。プーリングは遅いGCへの対策にはなりましたが、新しいVMのGC性能は従来よりも大きく改善されているので、プーリングは以前ほど魅力的ではなくなっています。
私は常に、動くものの中で最も単純なものを選びます。そしてオブジェクトプーリングはどうがんばっても単純にはならないものです。 たとえば、ここで投稿されたコードはスレッドセーフではありません。 あえてそう書いたのかも知れません。以前、マルチスレッドでアクセスされるコレクションなのに、意図的にsynchronized指定していないコードを見たことがあります。 とはいえ、この投稿に関してはうっかりしていただけのように見えます。
次の投稿では、サインとコサインの性能の話に戻ります。 投稿者は匿名ですが、どうもSunの開発部門の人のようです。 朗報としては、サイン、コサイン、タンジェント、ln、log10、平方根、そしてpowについて、現在はX86とAMD64のハードウェア演算を利用しているのことです。 匿名氏はほかに良い高速化の手法がないかと意見を募集しています。 リクエストをお持ちの方、今がチャンスですよ!!!
The ServerSideからは、分散キャッシュを構築するにあたってのアドバイスを求める投稿がありました。 このケースでは、彼に対するどの回答も、あてにならないキャッシュを作ってしまった経験の持ち主によるものばかりでした。 問題自体は単純に聞こえますが、実際には相当に複雑なものなのです。 もっとも良い解決策は、製品を買うことです。 この文脈ではJBossCacheとTangosolが推薦されていました。議論の中で触れている人はいませんでしたが、私からはGemFireをご紹介しておきます。
Weblogicサーバで全スレッドの一覧を得るにはどうすればいいでしょうか? 正しくBEA風にやるなら、ExecuteQueueRuntimeMBeanを取得して、これに問い合わせます。 Java風でいくなら、UNIXではサーバにkill -3を、Windowsではctrl-breakを送ります。
最後にご紹介するスレッドは、最初はRMI対JavaSpacesの話で始まったのですが、最後にはRMI対生ソケットになってしまいました! RMIの性能はリリースのたびに良くなっています。 しかし、生ソケットでのデータ送信ほどに高速になることはあり得ないかもしれません。 とはいえ、RMIとはリモートメソッド呼び出しの略です。 すいません、みなさんはとっくにご存じでしょうが、一応強調しておきたいのです。 RMIはリモートのオブジェクトとの強調動作のためのもので、生ソケットはデータ送信のためのものです。 これら2つの機構は明らかに抽象化のレベルが違います。 一方は私たちの仕事の基盤である、オブジェクト指向プログラミングというパラダイムを支えるものです。もう一方は単純なサービスです。 つまり設計という点では、RMIを使うのが理にかなっています。 RMIでは必要な性能が得られないのであれば、いつでも拡張する(そのような実装をいくつか見たことがあります)、または性能のでない部分のコードを生ソケットに置き換えることができます。 いつも言っているように、設計上の判断は、中途半端な最適化の懸念に必ず優先するべきなのです。
先月、私が気に入っているJavaのディスカッショングループはどれか、という質問を受けました。お答えすると、straight-talking javaというグループで、Yahooグループにあります。 時折話が脱線することでも知られているので、見に行かれる場合はJava以外の話題が出ていても驚かないでくださいね。
「昔、あるeコマースサイトでは値段を含めた買い物かごの情報をフォームのhiddenフィールドに格納していた。 このため、HTMLとHTTPに詳しいユーザーなら誰でも、あらゆる商品を1セントで購入できるような単純な攻撃が可能になっていた。」(Brian Goetz)
いやあ、これぞ商業webサイトの黎明期ですなあ。この純朴さと単純さ。そしてあまりにもバカすぎる。
8月だ。今月は読者のみなさんとの交流について、いくつかご紹介しようと思う。
Huttさん、助けてください。 'java Hello World'というコマンドでプログラムを起動したのですが、'Exception in thread "main" java.lang.NoClassDefFoundError: Hello'というエラーになってしまいます。 業務に使うアプリなので、今すぐチューニングして動かせるようにしないといけないんです。--Questor
Questorさん、それはおかしな問題です。 あなたのチューニングが完璧だったので、アプリケーションの動作速度が速すぎるため、このJVMがクラスを読むことができないのです。 JVMはハードディスクの中からクラスを探しますが、目当てのクラスが見付かる頃にはすでに次のクラスに言ってしまっているので、最初の一つ目を見失ってしまうのです。 JVMの動作を十分に遅くすれば、クラスを見つけられるようになり、NoClassDefFoundErrorもなくなるでしょう。 一番いい方法は、コンピュータのスピーカーの音量を最大にして、ホワイトノイズか、そのような音源が手元になければTangerine Dreamあたりを再生することです。 こうするとJVMの動作が遅くなることが知られています。 遅延効果が薄れるので、音量を下げたりしてはだめです。 これをうまくいくまで続けてください。 グッドラック。 --Javva The Hutt
The Huttさん、あなたはJavaプログラムのチューニングに関して深い知識をお持ちですね。私にご助力をいただけないでしょうか? Java SDKには「高速化」ボタンなるものがあると聞いたのですが、見つけることができないのです。 --Mr. Enquirer
Mr. Enquirerさん、そのボタンは間違いなくありますが、私たちのようなパフォーマンスのエキスパートはそういうものを秘密にしておくことを好むのです。 みんながボタンの存在に気づいたら、私たちは失業してしまいますからね。 --Javva The Hutt
The Huttさん、どうかボタンの探し方を教えてください。 教えていただいたことは秘密にします。絶対に誰にもしゃべりませんから。 --Mr. Enquirer
Mr. Enquirerさん、ボタンのことは大事な秘密ですが、毎年一人にはその秘密を教えることにしています。 今年はあなたをその一人として選ぶことにしました。 実は、ボタンを使うためのコードは背景色と同じ色で書かれている、という仕掛けがあります。 しかし秘密を守るための仕掛けはこれだけではないんです。 java.langパッケージにSecret.javaという特別な隠しファイルがあります。 これはソースには含まれておらず、JREのjarファイルに入っています。 参照するにはJREのjarファイルを開いて、java.lang以下のクラスをすべて削除するしかありません。 そしてjavaの実行ファイルを走らせる必要があります。これで隠しファイルが生成されます。 偶然これらの一連の操作を実行してしまった人に気づかれないよう、JVMはSecret.javaファイルを正常に書き出すと以下のメッセージを出力します。
"Error occurred during initialization of VM java/lang/NoClassDefFoundError: java/lang/Object"
これでOSのシステムディレクトリに"隠し"ファイルとしてSecret.javaができるはずです。 このファイルを開き、前景色を背景色とは別の色にして、コンパイルします。 これで「高速動作」ボタンにアクセスできるようになります。 どうぞこのことはご内密に。--Javva The Hutt
The Huttさん、教えていただいた手順に従ったところ、すべてご指示の通りの結果になりましたが、OSのどこにもSecret.javaファイルは見付かりません。どこにあるのか教えていただけないでしょうか? また、javaも動かなくなってしまいました。 --Mr. Enquirer
Mr. Enquirerさん、残念ながらお話の内容はわかりかねます。 Secret.javaファイルなるものは全く存じません。 別の担当者とお間違えではないでしょうか? 貴殿からはこれまでに一度もメールをいただいたことはありません。 --Javva The Hutt
「昔ながらのチューニングパラメータ」とは何でしょうか?
これはアプリケーションの中で変更可能な引数や変数のことです。様々な値を設定することができ、ある値にすると性能が良くなる、というものです。 この引数もしくは変数は異なる定義済みの値のセット、またはたいていの場合連続した範囲の値をとることができます。 それぞれの異なる値はアプリケーションの性能に変化を及ぼします。チューニングとは、これらの設定可能な値の範囲内で観測される様々なパフォーマンスを分析し、最適あるいは最適に近い値を発見する作業のことです。
いやはや、ずいぶん長い口上でした。 ではもう少しわかりやすいサンプルを見ていきましょう。JVMのヒープサイズを例に取ります。 理想的には古い世代のGC発生を避けるために、アプリケーションの中でオブジェクト生成、若い世代のパラメータ、GCアルゴリズムをチューニングします。 しかしたいていの場合、少なくともある程度アプリケーションのバージョンが進み、適切なコードのチューニングができるようになるまで、古い世代のGCが起きるのは避けられません。 こうした状況では、最適なヒープサイズを見つけだす必要があるかも知れません。 単純に「できるだけ大きい値」にしてはまずいのです。
それは、ヒープサイズはクラスをチューニングするパラメータであるためです。 古い世代のGCにかかる時間はヒープサイズに比例するため、値が大きければGCによる停止の期間は長くなります。 値がとても大きい場合、OSがヒープをページングする可能性も大きくなってしまい、性能の大幅な低下につながります。 値が小さければGCによる停止も短くなりますが、GCの起きる回数は多くなり、GCに費やされる時間の合計が増えるため、結果としてアプリケーション全般の性能も悪化します。 値が非常に小さい場合、ヒープのスラッシングが発生することがあります。メモリの空きが少なすぎるため、オブジェクトを作ったとたんにそれを回収して空きを作らなくてはいけない、という状況のことです。結果として、値が小さすぎるとメモリ不足エラーにつながります。
すると、GCの停止時間とGCの合計処理時間の両方が最小となるような、大きすぎと小さすぎの間のどこかにある一点がちょうどいい(goldilocks値)ということになります。 これが古典的なチューニングパラメータです。 性能を検証するために適当な値を選び、どの値がよりよい性能を示すか、できればなぜその値が良い性能につながるのか(必須ではありませんが)を分析し、ヒープにとってさらに適した値に近づけていくのです。
古典的チューニングパラメータはほとんどどんなアプリケーションにも存在します(最適なバッファのサイズ、キャッシュのサイズ、セッションやキャッシュ要素の持続時間など) アルゴリズムを改善することで、こうしたチューニングが不要になることもあります。 たとえば、並列GCアルゴリズムは停止時間を最小に抑えるために、アプリケーションに対して並列的にGCを実行します。 このケースでは、ヒープのチューニングがもう必要ではなくなる可能性があります。ページングを起こすことなくOS上で扱える最も大きなサイズが、多くの場合に適切な値となるでしょう。
http://www-106.ibm.com/developerworks/websphere/techjournal/0408_bernal/0408_bernal.html
WebSphereでコマンドをキャッシュする
(Page last updated August 2004, Added 2004-09-29, Author Joey
Bernal Varaprasad Bhatta, Publisher IBM). Tips:
http://www.devx.com/Java/Article/20791
大規模バッチトランザクションの処理
(Page last updated April 2004, Added 2004-09-29, Author Lara D'Abreo, Publisher DevX.com). Tips: