あえて言うほどではない 文字列結合 Java 9 でもやってみた編

Pocket

こんにちは。梶原です。
これは TECHSCORE Advent Calendar 2017 の9日目の記事です。

Java 9 がリリースされ、Java 8 の End of Life を本腰入れて考え始めなければいけない今日この頃。
いま更感が半端ないですが、文字列結合のベンチマーク レッツトライ (`・ω・´)です。

元ネタは我が TECHSCORE ブログが誇る人気シリーズ「あえて言うほどではない」の過去記事(2012年11月29日)です。
あえて言うほどではない 文字列結合 Java編

  • プラス演算子
  • String#concat()
  • StringBuffer
  • StringBuilder

上記の方法で結合する処理を 10万回ループ中に実施、Java 5, Java 6, Java 7 で計測しています。結果も非常に興味深いです。過去記事も是非ご覧ください!

今回は Java 7, Java 8, Java 9(執筆時点 9.0.1)での計測です。
ベンチマーク計測には jmh を利用しました。
OpenJDK: jmh
では、やってみましょう。

計測用コード

文字列結合を10万回ループ中に実施。計測はウォームアップ・イテレート回数ともに jmh のデフォルト値である 20 回で行いました。

実行結果ログのうち ベンチマークが記述された部分は以下のように出力されます。

test1(プラス演算子による結合)の実行には平均 5647.217 ミリ秒かかった、と読みます。

計測結果

計測用コードを 60 回実行した平均値(読みやすいように四捨五入しています)を、以下の表にまとめました。単位はミリ秒です。

ループ10万回 Java 7 Java 8 Java 9
プラス演算子 6152.228 5614.730 2009.843
String#concat() 4407.053 4304.561 2193.133
StringBuffer 1.523 1.088 1.345
StringBuilder 1.112 1.083 0.415

何といっても目を引くのは、Java 9 の処理速度。
プラス演算子と String#concat() と StringBuilder、素晴らしいですよね。今回のような簡単な計測方法でも、さらっと Java 8 の 2倍速を叩き出しました。

Java 9 における文字列の処理効率向上「Compact Strings」と「Indify String Concatenation」

JEP 254: Compact Strings
JEP 280: Indify String Concatenation

まずは、プラス演算子の計測結果に注目します。Indify String Concatenation の影響を受けて爆速化したと思われます。

String (Java Platform SE 8 )
Java 8 までの常識は「文字列連結はStringBuilder (またはStringBuffer)クラスとそのappendメソッドを使って実装されています」でした。

String (Java SE 9 & JDK 9 )
ところが、Java 9 では上記の記述が API ドキュメントからまるっと削除され、代わりの記述に「文字列の連結と変換の詳細については、「Java™言語仕様」を参照してください」とあります。何と、StringBuilder を使わなくなったのです。Java 9 からの新常識。ぜひ覚えておきたいものです。

詳細はまた別の機会に書こうと思いますが、ざっくりと言うと「予め結合する文字列の長さの byte 配列を確保して文字列を詰めていき、String に変換する」が Java 9 プラス演算子の処理の中身です。

次に、プラス演算子と String#concat() と StringBuilder の処理効率向上の素、Compact Strings に簡単に触れておきます。

これまでの実装では、文字列を 2 バイトの char 配列として格納していました。
Java の中の人は考えました。「半角英数字や記号・数字など、文字コード Latin-1 を取り扱うことがほとんどなんだから 1 バイト 無駄にしている」と。「だったら、Latin-1 (1 バイト)で持とうよ」。
結果、文字列を Latin-1、1 バイトの byte 配列として格納するようなった= Compact Strings です。Latin-1 以外の文字、例えば日本語「あ」の場合ではこれまでどおり 2 バイトの UTF-16 として格納します。

たしかに、ぎゅっとコンパクト化されました。
でも、私達は漢字とひらがなデータを取り扱うことも多いのです...
残念な結果が予想されますが、やってみました。 ひらがな「あああ」文字列結合のベンチマーク レッツトライ (`・ω・´)です。

ループ10万回 Java 7 Java 8 Java 9
プラス演算子 6027.036 5511.940 4033.945
String#concat() 4219.871 4245.241 4428.548
StringBuffer 1.483 1.158 1.636
StringBuilder 1.095 1.066 0.721

Latin-1 以外の文字列では Compact Strings の恩恵をおおいに受ける、ということはなさそうです。
Java 8 から Java 9 に変更したのに思ったほど処理速度が上がらない、というケースも出てきそうです。当たり前の話ですが、内部処理を理解してデータに沿った最適化が必要になるということでしょう。

それにしても StringBuffer。Java 8 よりも Java 9 の方が遅くなっています。いったいどうしたんでしょう...(´・ω・`)
また機会があれば調べてみたいと思います。

まとめ

  • Java 9 では文字列結合は高速になる(ただし Latin-1 に限る !!)
  • Java 9 ではプラス演算子の内部処理は StringBuilder (またはStringBuffer) ではない
  • バージョンUP=処理の高速化ではなく、内部処理を理解した最適化が必要になる

Java 9 の新機能はまだまだあります。驚きと喜びに浸りつつ、少しずつでも読み解いて行きたいと思います。

Pocket

Comments are closed, but you can leave a trackback: Trackback URL.