|
文字列操作 スレッドセーフ vs パフォーマンス StringBuilder |
||||
|
||||
StringBuilder クラスは Tiger で導入されたクラスなのですが、なんと機能は StringBuffer クラスとまったく同じなのです。 それじゃ、なぜ今になって新しいクラスを作ったのでしょうか。 それはスレッドセーフとパフォーマンスに関連します。 StringBuffer クラスのすべてのメソッドは synchronized になっており、同期化されています。このため、複数のスレッドから使用される場合でも、安全に使用することができます。 その一方で同期化にはコストがかかります。簡単にいえばメソッドを synchronized にすると遅くなってしまうのです。 しかし、StringBuffer を使うときに本当にスレッドセーフが必要ですか? 私が書いてきたコードには StringBuffer クラスにスレッドセーフが必要だったケースはほとんどありませんでした。それなのにわざわざ同期化した StringBuffer クラスを 使う必要はありません。 そこで、登場したのがスレッドセーフではない StringBuilder クラスです。 StringBuffer クラスと StringBuilder クラスの違いは、ちょうど java.util.Vector クラスと java.util.ArrayList クラスの違いと同じようなものです。 機能は同じなので、スレッドセーフが必要かどうかで選べばいいと思います。
|
|
||||||
同期化されていない分 StringBuilder クラスのパフォーマンスは StringBuffer クラスより高いはずです。そこで、簡単なアプリケーションで試してみました。
単に append メソッドを何度も呼んでいるだけです。
これを PentiumM 1.7MHz メモリ 512MB の Windows XP のマシンで実行してみました。
はじめの 813 ms は省いたとしても、だいたい 2 割から 3 割りぐらい高速になっているようです。 現状の HotSpot の実装では synchronized のコストはずいぶん減っているので、こんなものなのでしょう。私はもっと差が少ないかと思っていたぐらいです。
|
|
||||
文字列の連結には StringBuffer クラスを使うようにとよくいわれますが、Tiger では javac がうまく変換してくれます。たとえば、次に示すソースだとどうなるでしょうか。
これをコンパイルし、Jad で逆コンパイルするとこうなりました。見やすくするため少しだけ編集してあります。
javac が StringBuilder を使って最適化をしてくれます。こんな風にも使われているのですね。
|
|
||||||||||
StringBuilder クラスと StringBuffer クラスのソースを見比べると面白いことがわかります。 J2SE 1.4 までは StringBuffer クラスは通常のクラスでしたが、Tiger では AbstractStringBuilder クラスの派生クラスになりました。
もちろん、AbstractStringBuilder クラスは Tiger で導入されたものです。また、Appendable インタフェースも Tiger で導入されました。 たとえば、StringBuffer#append(String str) メソッドと StringBuilder#append(String str) メソッドを比較してみましょう。
違いは synchronized であるかどうかだけで、実際の処理は AbstractStringBuilder が行っていることがわかります。AbstractStringBuilder クラスの append(String str) メソッドも見てみましょう。
参考までに J2SE 1.4.2 の StringBuffer#append(String str) メソッドは次のように定義されています。
null の時の処理など少しだけ違いますが、基本的には同じ処理をしています。 つまり、J2SE 1.4 までの StringBuffer クラスの処理を Tiger では AbstractStringBuilder クラスにうつしてしまったということです。 そして、StringBuffer クラスと StringBuilder クラスは処理を AbstractStringBuilder クラスに委譲してしまっています。この 2 つのクラスの違いは単に synchronized がついているかいないかというという違いだけになっているわけです。 ここで使用されたリファクタリングなど、Java のソースコードはお手本にしたいテクニックが満載されていますね。 あるバージョンのソースを見るだけでもいいとは思うのですが、今回の例のように複数のバージョンのソースを追いかけることでコードをどのように洗練させていくかを知ることもできるのです。
|
|
||||
単に synchronized がはずされただけですが、StringBuilder クラスは使う場面が多そうです。 複数のスレッドから文字列操作を行うのであれば StringBuffer クラス、単一スレッドであれば StringBuilder クラスという使い分けをおこなうようにすれば OK です。 実際にアプリケーションをプロファイリングすると、文字列操作は頻度が高い場合が多いのではないでしょうか。そんなときに、StringBuffer クラスから StringBuilder クラスに変更するだけでパフォーマンスが向上するわけですから、使わない理由はありません。 今回使用したサンプルはここからダウンロードできます。
(Mar. 2004) |
|