読者です 読者をやめる 読者になる 読者になる

『Re: InputStreamからStringへの変換』の記事のコードはNIO使えばもっと速くなるんじゃないかと思ってベンチマーク取ってみた

言及対象の記事

Re: InputStreamからStringへの変換

この記事は当時Javaのことが何もわかっていなかった自分にとって大変助かった記事だし、 その後仕事でも度々助けられました。本当に感謝してます。

あの記事では自分が当時Java初心者だったこともあって、とても分かりやすいコードを書いてくれたのだけど、 ふとNIOを使ったほうが速くなるんじゃないかと思ったので試してみた。 …軽い気持ちで試してみようと思ったつもりが、異常値弾いてちゃんとベンチマークするまで結構時間がかかってしまった。 正直最後の結果でもちゃんとベンチマークできているか不安なので、コードにforkなりコメント欄なりツッコミをくれるとうれしいです。

TL;DR

先に結果だけ貼っておきます。なおコードは長いので記事の最後にあります。 (100回各メソッドを実行してそのうちの30回の中央値を取るベンチマークプログラムを3回実行した結果)

Result:
  convertInputStreamToString() (Median 30/100) : 42811 nanoseconds
  convertInputStreamToStringWithCharset() (Median 30/100) : 41121 nanoseconds
  convertInputStreamToStringNio() (Median 30/100) : 36258 nanoseconds
  convertInputStreamToStringNioWithCharset() (Median 30/100) : 36126 nanoseconds
  convertInputStreamToStringNioDirectBuffer() (Median 30/100) : 45299 nanoseconds
  convertInputStreamToStringNioDirectBufferWithCharset() (Median 30/100) : 45375 nanoseconds

Result:
  convertInputStreamToString() (Median 30/100) : 42545 nanoseconds
  convertInputStreamToStringWithCharset() (Median 30/100) : 40247 nanoseconds
  convertInputStreamToStringNio() (Median 30/100) : 35423 nanoseconds
  convertInputStreamToStringNioWithCharset() (Median 30/100) : 34986 nanoseconds
  convertInputStreamToStringNioDirectBuffer() (Median 30/100) : 43989 nanoseconds
  convertInputStreamToStringNioDirectBufferWithCharset() (Median 30/100) : 45603 nanoseconds

Result:
  convertInputStreamToString() (Median 30/100) : 43343 nanoseconds
  convertInputStreamToStringWithCharset() (Median 30/100) : 41216 nanoseconds
  convertInputStreamToStringNio() (Median 30/100) : 35727 nanoseconds
  convertInputStreamToStringNioWithCharset() (Median 30/100) : 35233 nanoseconds
  convertInputStreamToStringNioDirectBuffer() (Median 30/100) : 45869 nanoseconds
  convertInputStreamToStringNioDirectBufferWithCharset() (Median 30/100) : 45755 nanoseconds

比較対象

  1. thincaさんが教えてくれたメソッド (convertInputStreamToString())
  2. thincaさんが教えてくれたメソッドをCharset指定できるようにしたメソッド (convertInputStreamToStringWithCharset())
  3. NIO使うようにしたメソッド(allocate()版) (convertInputStreamToStringNio())
  4. NIO使うようにしたメソッド(allocate()版)をCharset指定できるようにしたメソッド (convertInputStreamToStringNioWithCharset())
  5. NIO使うようにしたメソッド(allocateDirect()版) (convertInputStreamToStringNioDirectBuffer())
  6. NIO使うようにしたメソッド(allocateDirect()版)をCharset指定できるようにしたメソッド (convertInputStreamToStringNioDirectBufferWithCharset())

実行環境

f:id:tyru:20150330035049p:plain

  • Eclipse + JDK 1.8.0_40
  • テスト用ファイル:「0」の文字をひたすら32MiB分書き込んだファイル
  • それぞれの比較対象メソッド(new FileInputStream("テスト用ファイル"));

1回だけだとファイルの内容がOSのキャッシュに乗ってないと思ったので2回ループして試した結果

(COUNT = 10_0000, RESULT_TIME_UNIT = TimeUnit.MILLISECONDS)

Result:
  convertInputStreamToString() : 4023 milliseconds
  convertInputStreamToStringWithCharset() : 4072 milliseconds
  convertInputStreamToStringNio() : 3144 milliseconds
  convertInputStreamToStringNioWithCharset() : 3859 milliseconds

Result:
  convertInputStreamToString() : 4283 milliseconds
  convertInputStreamToStringWithCharset() : 4144 milliseconds
  convertInputStreamToStringNio() : 3155 milliseconds
  convertInputStreamToStringNioWithCharset() : 4267 milliseconds

ループした時間を求めるのではなく個別に計測した結果を表示させてみた

やはり何回か実行すると値にばらつきがあり、正しい計測ができていないと感じたので、 COUNT回ループした時間を求めるのではなく、個別に計測してすべての値を表示させてみた。 案の定異常値がちらほら見られた。最初にとても遅いのは冒頭でも言ったようにファイルの内容がOSのキャッシュに乗っていないためと思われる。 (COUNT = 100, RESULT_TIME_UNIT = TimeUnit.NANOSECONDS)

メソッドを100回実行した結果を全て載せていて長いので注意。

Result:
  convertInputStreamToString() : 1739653710 nanoseconds
  convertInputStreamToString() : 329345 nanoseconds
  convertInputStreamToString() : 67237 nanoseconds
  convertInputStreamToString() : 51282 nanoseconds
  convertInputStreamToString() : 50142 nanoseconds
  convertInputStreamToString() : 47863 nanoseconds
  convertInputStreamToString() : 49572 nanoseconds
  convertInputStreamToString() : 47293 nanoseconds
  convertInputStreamToString() : 50142 nanoseconds
  convertInputStreamToString() : 49003 nanoseconds
  convertInputStreamToString() : 49003 nanoseconds
  convertInputStreamToString() : 48433 nanoseconds
  convertInputStreamToString() : 50143 nanoseconds
  convertInputStreamToString() : 48433 nanoseconds
  convertInputStreamToString() : 49573 nanoseconds
  convertInputStreamToString() : 47863 nanoseconds
  convertInputStreamToString() : 50142 nanoseconds
  convertInputStreamToString() : 50712 nanoseconds
  convertInputStreamToString() : 52421 nanoseconds
  convertInputStreamToString() : 50142 nanoseconds
  convertInputStreamToString() : 52422 nanoseconds
  convertInputStreamToString() : 48433 nanoseconds
  convertInputStreamToString() : 49572 nanoseconds
  convertInputStreamToString() : 49573 nanoseconds
  convertInputStreamToString() : 50713 nanoseconds
  convertInputStreamToString() : 51282 nanoseconds
  convertInputStreamToString() : 50712 nanoseconds
  convertInputStreamToString() : 51851 nanoseconds
  convertInputStreamToString() : 50142 nanoseconds
  convertInputStreamToString() : 47863 nanoseconds
  convertInputStreamToString() : 52421 nanoseconds
  convertInputStreamToString() : 51282 nanoseconds
  convertInputStreamToString() : 246154 nanoseconds
  convertInputStreamToString() : 51852 nanoseconds
  convertInputStreamToString() : 47863 nanoseconds
  convertInputStreamToString() : 52422 nanoseconds
  convertInputStreamToString() : 131624 nanoseconds
  convertInputStreamToString() : 68946 nanoseconds
  convertInputStreamToString() : 58119 nanoseconds
  convertInputStreamToString() : 56980 nanoseconds
  convertInputStreamToString() : 64957 nanoseconds
  convertInputStreamToString() : 62108 nanoseconds
  convertInputStreamToString() : 69516 nanoseconds
  convertInputStreamToString() : 61538 nanoseconds
  convertInputStreamToString() : 62679 nanoseconds
  convertInputStreamToString() : 61538 nanoseconds
  convertInputStreamToString() : 62678 nanoseconds
  convertInputStreamToString() : 56980 nanoseconds
  convertInputStreamToString() : 58119 nanoseconds
  convertInputStreamToString() : 59259 nanoseconds
  convertInputStreamToString() : 54701 nanoseconds
  convertInputStreamToString() : 55841 nanoseconds
  convertInputStreamToString() : 55841 nanoseconds
  convertInputStreamToString() : 54131 nanoseconds
  convertInputStreamToString() : 54700 nanoseconds
  convertInputStreamToString() : 56980 nanoseconds
  convertInputStreamToString() : 59829 nanoseconds
  convertInputStreamToString() : 59259 nanoseconds
  convertInputStreamToString() : 58690 nanoseconds
  convertInputStreamToString() : 418803 nanoseconds
  convertInputStreamToString() : 90028 nanoseconds
  convertInputStreamToString() : 59260 nanoseconds
  convertInputStreamToString() : 56410 nanoseconds
  convertInputStreamToString() : 54701 nanoseconds
  convertInputStreamToString() : 60969 nanoseconds
  convertInputStreamToString() : 59829 nanoseconds
  convertInputStreamToString() : 63818 nanoseconds
  convertInputStreamToString() : 70655 nanoseconds
  convertInputStreamToString() : 59259 nanoseconds
  convertInputStreamToString() : 60969 nanoseconds
  convertInputStreamToString() : 43875 nanoseconds
  convertInputStreamToString() : 39886 nanoseconds
  convertInputStreamToString() : 39886 nanoseconds
  convertInputStreamToString() : 38747 nanoseconds
  convertInputStreamToString() : 38747 nanoseconds
  convertInputStreamToString() : 38747 nanoseconds
  convertInputStreamToString() : 38177 nanoseconds
  convertInputStreamToString() : 39316 nanoseconds
  convertInputStreamToString() : 39886 nanoseconds
  convertInputStreamToString() : 39316 nanoseconds
  convertInputStreamToString() : 39886 nanoseconds
  convertInputStreamToString() : 38176 nanoseconds
  convertInputStreamToString() : 40456 nanoseconds
  convertInputStreamToString() : 38747 nanoseconds
  convertInputStreamToString() : 38747 nanoseconds
  convertInputStreamToString() : 38747 nanoseconds
  convertInputStreamToString() : 41596 nanoseconds
  convertInputStreamToString() : 38177 nanoseconds
  convertInputStreamToString() : 38746 nanoseconds
  convertInputStreamToString() : 41025 nanoseconds
  convertInputStreamToString() : 38746 nanoseconds
  convertInputStreamToString() : 38746 nanoseconds
  convertInputStreamToString() : 41026 nanoseconds
  convertInputStreamToString() : 40456 nanoseconds
  convertInputStreamToString() : 42165 nanoseconds
  convertInputStreamToString() : 39886 nanoseconds
  convertInputStreamToString() : 53561 nanoseconds
  convertInputStreamToString() : 39316 nanoseconds
  convertInputStreamToString() : 42165 nanoseconds
  convertInputStreamToString() : 39886 nanoseconds
  convertInputStreamToStringWithCharset() : 1651830398 nanoseconds
  convertInputStreamToStringWithCharset() : 384615 nanoseconds
  convertInputStreamToStringWithCharset() : 105413 nanoseconds
  convertInputStreamToStringWithCharset() : 74644 nanoseconds
  convertInputStreamToStringWithCharset() : 62678 nanoseconds
  convertInputStreamToStringWithCharset() : 60969 nanoseconds
  convertInputStreamToStringWithCharset() : 56410 nanoseconds
  convertInputStreamToStringWithCharset() : 54701 nanoseconds
  convertInputStreamToStringWithCharset() : 60968 nanoseconds
  convertInputStreamToStringWithCharset() : 70655 nanoseconds
  convertInputStreamToStringWithCharset() : 70086 nanoseconds
  convertInputStreamToStringWithCharset() : 71225 nanoseconds
  convertInputStreamToStringWithCharset() : 67806 nanoseconds
  convertInputStreamToStringWithCharset() : 70655 nanoseconds
  convertInputStreamToStringWithCharset() : 70655 nanoseconds
  convertInputStreamToStringWithCharset() : 116239 nanoseconds
  convertInputStreamToStringWithCharset() : 187464 nanoseconds
  convertInputStreamToStringWithCharset() : 74074 nanoseconds
  convertInputStreamToStringWithCharset() : 61538 nanoseconds
  convertInputStreamToStringWithCharset() : 60968 nanoseconds
  convertInputStreamToStringWithCharset() : 58689 nanoseconds
  convertInputStreamToStringWithCharset() : 56980 nanoseconds
  convertInputStreamToStringWithCharset() : 58120 nanoseconds
  convertInputStreamToStringWithCharset() : 60968 nanoseconds
  convertInputStreamToStringWithCharset() : 58689 nanoseconds
  convertInputStreamToStringWithCharset() : 62108 nanoseconds
  convertInputStreamToStringWithCharset() : 60399 nanoseconds
  convertInputStreamToStringWithCharset() : 58119 nanoseconds
  convertInputStreamToStringWithCharset() : 60399 nanoseconds
  convertInputStreamToStringWithCharset() : 72935 nanoseconds
  convertInputStreamToStringWithCharset() : 67236 nanoseconds
  convertInputStreamToStringWithCharset() : 62109 nanoseconds
  convertInputStreamToStringWithCharset() : 62678 nanoseconds
  convertInputStreamToStringWithCharset() : 60399 nanoseconds
  convertInputStreamToStringWithCharset() : 65527 nanoseconds
  convertInputStreamToStringWithCharset() : 63818 nanoseconds
  convertInputStreamToStringWithCharset() : 282621 nanoseconds
  convertInputStreamToStringWithCharset() : 43304 nanoseconds
  convertInputStreamToStringWithCharset() : 37607 nanoseconds
  convertInputStreamToStringWithCharset() : 37606 nanoseconds
  convertInputStreamToStringWithCharset() : 36467 nanoseconds
  convertInputStreamToStringWithCharset() : 35897 nanoseconds
  convertInputStreamToStringWithCharset() : 36467 nanoseconds
  convertInputStreamToStringWithCharset() : 37607 nanoseconds
  convertInputStreamToStringWithCharset() : 37037 nanoseconds
  convertInputStreamToStringWithCharset() : 38177 nanoseconds
  convertInputStreamToStringWithCharset() : 34758 nanoseconds
  convertInputStreamToStringWithCharset() : 127635 nanoseconds
  convertInputStreamToStringWithCharset() : 87749 nanoseconds
  convertInputStreamToStringWithCharset() : 67806 nanoseconds
  convertInputStreamToStringWithCharset() : 56411 nanoseconds
  convertInputStreamToStringWithCharset() : 59829 nanoseconds
  convertInputStreamToStringWithCharset() : 55270 nanoseconds
  convertInputStreamToStringWithCharset() : 56980 nanoseconds
  convertInputStreamToStringWithCharset() : 53561 nanoseconds
  convertInputStreamToStringWithCharset() : 62678 nanoseconds
  convertInputStreamToStringWithCharset() : 54701 nanoseconds
  convertInputStreamToStringWithCharset() : 51851 nanoseconds
  convertInputStreamToStringWithCharset() : 62108 nanoseconds
  convertInputStreamToStringWithCharset() : 61538 nanoseconds
  convertInputStreamToStringWithCharset() : 51852 nanoseconds
  convertInputStreamToStringWithCharset() : 38177 nanoseconds
  convertInputStreamToStringWithCharset() : 37607 nanoseconds
  convertInputStreamToStringWithCharset() : 39316 nanoseconds
  convertInputStreamToStringWithCharset() : 37606 nanoseconds
  convertInputStreamToStringWithCharset() : 35898 nanoseconds
  convertInputStreamToStringWithCharset() : 35898 nanoseconds
  convertInputStreamToStringWithCharset() : 34758 nanoseconds
  convertInputStreamToStringWithCharset() : 35327 nanoseconds
  convertInputStreamToStringWithCharset() : 40456 nanoseconds
  convertInputStreamToStringWithCharset() : 37606 nanoseconds
  convertInputStreamToStringWithCharset() : 36467 nanoseconds
  convertInputStreamToStringWithCharset() : 35327 nanoseconds
  convertInputStreamToStringWithCharset() : 37037 nanoseconds
  convertInputStreamToStringWithCharset() : 36467 nanoseconds
  convertInputStreamToStringWithCharset() : 36468 nanoseconds
  convertInputStreamToStringWithCharset() : 37037 nanoseconds
  convertInputStreamToStringWithCharset() : 37037 nanoseconds
  convertInputStreamToStringWithCharset() : 36467 nanoseconds
  convertInputStreamToStringWithCharset() : 34757 nanoseconds
  convertInputStreamToStringWithCharset() : 37037 nanoseconds
  convertInputStreamToStringWithCharset() : 38177 nanoseconds
  convertInputStreamToStringWithCharset() : 37037 nanoseconds
  convertInputStreamToStringWithCharset() : 36467 nanoseconds
  convertInputStreamToStringWithCharset() : 36467 nanoseconds
  convertInputStreamToStringWithCharset() : 37037 nanoseconds
  convertInputStreamToStringWithCharset() : 37037 nanoseconds
  convertInputStreamToStringWithCharset() : 38746 nanoseconds
  convertInputStreamToStringWithCharset() : 37607 nanoseconds
  convertInputStreamToStringWithCharset() : 37037 nanoseconds
  convertInputStreamToStringWithCharset() : 38746 nanoseconds
  convertInputStreamToStringWithCharset() : 40456 nanoseconds
  convertInputStreamToStringWithCharset() : 38176 nanoseconds
  convertInputStreamToStringWithCharset() : 37037 nanoseconds
  convertInputStreamToStringWithCharset() : 38746 nanoseconds
  convertInputStreamToStringWithCharset() : 38176 nanoseconds
  convertInputStreamToStringWithCharset() : 35897 nanoseconds
  convertInputStreamToStringWithCharset() : 39316 nanoseconds
  convertInputStreamToStringWithCharset() : 39886 nanoseconds
  convertInputStreamToStringWithCharset() : 36467 nanoseconds
  convertInputStreamToStringNio() : 1565111359 nanoseconds
  convertInputStreamToStringNio() : 370371 nanoseconds
  convertInputStreamToStringNio() : 100285 nanoseconds
  convertInputStreamToStringNio() : 59259 nanoseconds
  convertInputStreamToStringNio() : 54131 nanoseconds
  convertInputStreamToStringNio() : 54131 nanoseconds
  convertInputStreamToStringNio() : 54701 nanoseconds
  convertInputStreamToStringNio() : 55271 nanoseconds
  convertInputStreamToStringNio() : 54131 nanoseconds
  convertInputStreamToStringNio() : 55271 nanoseconds
  convertInputStreamToStringNio() : 52422 nanoseconds
  convertInputStreamToStringNio() : 54132 nanoseconds
  convertInputStreamToStringNio() : 55841 nanoseconds
  convertInputStreamToStringNio() : 55271 nanoseconds
  convertInputStreamToStringNio() : 53561 nanoseconds
  convertInputStreamToStringNio() : 55270 nanoseconds
  convertInputStreamToStringNio() : 54131 nanoseconds
  convertInputStreamToStringNio() : 52422 nanoseconds
  convertInputStreamToStringNio() : 52992 nanoseconds
  convertInputStreamToStringNio() : 54131 nanoseconds
  convertInputStreamToStringNio() : 52991 nanoseconds
  convertInputStreamToStringNio() : 47863 nanoseconds
  convertInputStreamToStringNio() : 51282 nanoseconds
  convertInputStreamToStringNio() : 52422 nanoseconds
  convertInputStreamToStringNio() : 51851 nanoseconds
  convertInputStreamToStringNio() : 52991 nanoseconds
  convertInputStreamToStringNio() : 52991 nanoseconds
  convertInputStreamToStringNio() : 51852 nanoseconds
  convertInputStreamToStringNio() : 54131 nanoseconds
  convertInputStreamToStringNio() : 51282 nanoseconds
  convertInputStreamToStringNio() : 50713 nanoseconds
  convertInputStreamToStringNio() : 51282 nanoseconds
  convertInputStreamToStringNio() : 49572 nanoseconds
  convertInputStreamToStringNio() : 50712 nanoseconds
  convertInputStreamToStringNio() : 51852 nanoseconds
  convertInputStreamToStringNio() : 52991 nanoseconds
  convertInputStreamToStringNio() : 50712 nanoseconds
  convertInputStreamToStringNio() : 51852 nanoseconds
  convertInputStreamToStringNio() : 53561 nanoseconds
  convertInputStreamToStringNio() : 68376 nanoseconds
  convertInputStreamToStringNio() : 191453 nanoseconds
  convertInputStreamToStringNio() : 58690 nanoseconds
  convertInputStreamToStringNio() : 55271 nanoseconds
  convertInputStreamToStringNio() : 52992 nanoseconds
  convertInputStreamToStringNio() : 248433 nanoseconds
  convertInputStreamToStringNio() : 38177 nanoseconds
  convertInputStreamToStringNio() : 33049 nanoseconds
  convertInputStreamToStringNio() : 31909 nanoseconds
  convertInputStreamToStringNio() : 31909 nanoseconds
  convertInputStreamToStringNio() : 31909 nanoseconds
  convertInputStreamToStringNio() : 31909 nanoseconds
  convertInputStreamToStringNio() : 31909 nanoseconds
  convertInputStreamToStringNio() : 31909 nanoseconds
  convertInputStreamToStringNio() : 31909 nanoseconds
  convertInputStreamToStringNio() : 31908 nanoseconds
  convertInputStreamToStringNio() : 31909 nanoseconds
  convertInputStreamToStringNio() : 31908 nanoseconds
  convertInputStreamToStringNio() : 31909 nanoseconds
  convertInputStreamToStringNio() : 31909 nanoseconds
  convertInputStreamToStringNio() : 31909 nanoseconds
  convertInputStreamToStringNio() : 32479 nanoseconds
  convertInputStreamToStringNio() : 31909 nanoseconds
  convertInputStreamToStringNio() : 31909 nanoseconds
  convertInputStreamToStringNio() : 31909 nanoseconds
  convertInputStreamToStringNio() : 31909 nanoseconds
  convertInputStreamToStringNio() : 31909 nanoseconds
  convertInputStreamToStringNio() : 32478 nanoseconds
  convertInputStreamToStringNio() : 31909 nanoseconds
  convertInputStreamToStringNio() : 32479 nanoseconds
  convertInputStreamToStringNio() : 31909 nanoseconds
  convertInputStreamToStringNio() : 31909 nanoseconds
  convertInputStreamToStringNio() : 31909 nanoseconds
  convertInputStreamToStringNio() : 31908 nanoseconds
  convertInputStreamToStringNio() : 31909 nanoseconds
  convertInputStreamToStringNio() : 31909 nanoseconds
  convertInputStreamToStringNio() : 31909 nanoseconds
  convertInputStreamToStringNio() : 31909 nanoseconds
  convertInputStreamToStringNio() : 31908 nanoseconds
  convertInputStreamToStringNio() : 31909 nanoseconds
  convertInputStreamToStringNio() : 31908 nanoseconds
  convertInputStreamToStringNio() : 32479 nanoseconds
  convertInputStreamToStringNio() : 31909 nanoseconds
  convertInputStreamToStringNio() : 32478 nanoseconds
  convertInputStreamToStringNio() : 32479 nanoseconds
  convertInputStreamToStringNio() : 32479 nanoseconds
  convertInputStreamToStringNio() : 32478 nanoseconds
  convertInputStreamToStringNio() : 32479 nanoseconds
  convertInputStreamToStringNio() : 31909 nanoseconds
  convertInputStreamToStringNio() : 32478 nanoseconds
  convertInputStreamToStringNio() : 31909 nanoseconds
  convertInputStreamToStringNio() : 31909 nanoseconds
  convertInputStreamToStringNio() : 32478 nanoseconds
  convertInputStreamToStringNio() : 31909 nanoseconds
  convertInputStreamToStringNio() : 31909 nanoseconds
  convertInputStreamToStringNio() : 32478 nanoseconds
  convertInputStreamToStringNio() : 31909 nanoseconds
  convertInputStreamToStringNio() : 32479 nanoseconds
  convertInputStreamToStringNio() : 33048 nanoseconds
  convertInputStreamToStringNio() : 32478 nanoseconds
  convertInputStreamToStringNio() : 32479 nanoseconds
  convertInputStreamToStringNioWithCharset() : 2269505830 nanoseconds
  convertInputStreamToStringNioWithCharset() : 357835 nanoseconds
  convertInputStreamToStringNioWithCharset() : 62678 nanoseconds
  convertInputStreamToStringNioWithCharset() : 39886 nanoseconds
  convertInputStreamToStringNioWithCharset() : 38746 nanoseconds
  convertInputStreamToStringNioWithCharset() : 38746 nanoseconds
  convertInputStreamToStringNioWithCharset() : 38176 nanoseconds
  convertInputStreamToStringNioWithCharset() : 38177 nanoseconds
  convertInputStreamToStringNioWithCharset() : 38177 nanoseconds
  convertInputStreamToStringNioWithCharset() : 38177 nanoseconds
  convertInputStreamToStringNioWithCharset() : 38176 nanoseconds
  convertInputStreamToStringNioWithCharset() : 38746 nanoseconds
  convertInputStreamToStringNioWithCharset() : 37607 nanoseconds
  convertInputStreamToStringNioWithCharset() : 103134 nanoseconds
  convertInputStreamToStringNioWithCharset() : 41026 nanoseconds
  convertInputStreamToStringNioWithCharset() : 39316 nanoseconds
  convertInputStreamToStringNioWithCharset() : 38746 nanoseconds
  convertInputStreamToStringNioWithCharset() : 38746 nanoseconds
  convertInputStreamToStringNioWithCharset() : 38746 nanoseconds
  convertInputStreamToStringNioWithCharset() : 38177 nanoseconds
  convertInputStreamToStringNioWithCharset() : 38747 nanoseconds
  convertInputStreamToStringNioWithCharset() : 38747 nanoseconds
  convertInputStreamToStringNioWithCharset() : 38177 nanoseconds
  convertInputStreamToStringNioWithCharset() : 38746 nanoseconds
  convertInputStreamToStringNioWithCharset() : 38176 nanoseconds
  convertInputStreamToStringNioWithCharset() : 38747 nanoseconds
  convertInputStreamToStringNioWithCharset() : 38747 nanoseconds
  convertInputStreamToStringNioWithCharset() : 38177 nanoseconds
  convertInputStreamToStringNioWithCharset() : 39316 nanoseconds
  convertInputStreamToStringNioWithCharset() : 39316 nanoseconds
  convertInputStreamToStringNioWithCharset() : 38746 nanoseconds
  convertInputStreamToStringNioWithCharset() : 38746 nanoseconds
  convertInputStreamToStringNioWithCharset() : 38746 nanoseconds
  convertInputStreamToStringNioWithCharset() : 38176 nanoseconds
  convertInputStreamToStringNioWithCharset() : 38746 nanoseconds
  convertInputStreamToStringNioWithCharset() : 39316 nanoseconds
  convertInputStreamToStringNioWithCharset() : 38746 nanoseconds
  convertInputStreamToStringNioWithCharset() : 38177 nanoseconds
  convertInputStreamToStringNioWithCharset() : 38177 nanoseconds
  convertInputStreamToStringNioWithCharset() : 39316 nanoseconds
  convertInputStreamToStringNioWithCharset() : 38747 nanoseconds
  convertInputStreamToStringNioWithCharset() : 38747 nanoseconds
  convertInputStreamToStringNioWithCharset() : 38177 nanoseconds
  convertInputStreamToStringNioWithCharset() : 38176 nanoseconds
  convertInputStreamToStringNioWithCharset() : 38746 nanoseconds
  convertInputStreamToStringNioWithCharset() : 38176 nanoseconds
  convertInputStreamToStringNioWithCharset() : 38746 nanoseconds
  convertInputStreamToStringNioWithCharset() : 38746 nanoseconds
  convertInputStreamToStringNioWithCharset() : 38177 nanoseconds
  convertInputStreamToStringNioWithCharset() : 38747 nanoseconds
  convertInputStreamToStringNioWithCharset() : 38177 nanoseconds
  convertInputStreamToStringNioWithCharset() : 38746 nanoseconds
  convertInputStreamToStringNioWithCharset() : 38746 nanoseconds
  convertInputStreamToStringNioWithCharset() : 38746 nanoseconds
  convertInputStreamToStringNioWithCharset() : 39316 nanoseconds
  convertInputStreamToStringNioWithCharset() : 38746 nanoseconds
  convertInputStreamToStringNioWithCharset() : 222792 nanoseconds
  convertInputStreamToStringNioWithCharset() : 36467 nanoseconds
  convertInputStreamToStringNioWithCharset() : 33048 nanoseconds
  convertInputStreamToStringNioWithCharset() : 33049 nanoseconds
  convertInputStreamToStringNioWithCharset() : 32479 nanoseconds
  convertInputStreamToStringNioWithCharset() : 31908 nanoseconds
  convertInputStreamToStringNioWithCharset() : 31909 nanoseconds
  convertInputStreamToStringNioWithCharset() : 31909 nanoseconds
  convertInputStreamToStringNioWithCharset() : 32478 nanoseconds
  convertInputStreamToStringNioWithCharset() : 31909 nanoseconds
  convertInputStreamToStringNioWithCharset() : 32479 nanoseconds
  convertInputStreamToStringNioWithCharset() : 31908 nanoseconds
  convertInputStreamToStringNioWithCharset() : 31909 nanoseconds
  convertInputStreamToStringNioWithCharset() : 31909 nanoseconds
  convertInputStreamToStringNioWithCharset() : 31909 nanoseconds
  convertInputStreamToStringNioWithCharset() : 32479 nanoseconds
  convertInputStreamToStringNioWithCharset() : 32478 nanoseconds
  convertInputStreamToStringNioWithCharset() : 33049 nanoseconds
  convertInputStreamToStringNioWithCharset() : 32479 nanoseconds
  convertInputStreamToStringNioWithCharset() : 32478 nanoseconds
  convertInputStreamToStringNioWithCharset() : 31909 nanoseconds
  convertInputStreamToStringNioWithCharset() : 32479 nanoseconds
  convertInputStreamToStringNioWithCharset() : 32478 nanoseconds
  convertInputStreamToStringNioWithCharset() : 32479 nanoseconds
  convertInputStreamToStringNioWithCharset() : 31909 nanoseconds
  convertInputStreamToStringNioWithCharset() : 32478 nanoseconds
  convertInputStreamToStringNioWithCharset() : 31909 nanoseconds
  convertInputStreamToStringNioWithCharset() : 33049 nanoseconds
  convertInputStreamToStringNioWithCharset() : 32478 nanoseconds
  convertInputStreamToStringNioWithCharset() : 31909 nanoseconds
  convertInputStreamToStringNioWithCharset() : 31909 nanoseconds
  convertInputStreamToStringNioWithCharset() : 32478 nanoseconds
  convertInputStreamToStringNioWithCharset() : 32479 nanoseconds
  convertInputStreamToStringNioWithCharset() : 31909 nanoseconds
  convertInputStreamToStringNioWithCharset() : 32478 nanoseconds
  convertInputStreamToStringNioWithCharset() : 32478 nanoseconds
  convertInputStreamToStringNioWithCharset() : 31909 nanoseconds
  convertInputStreamToStringNioWithCharset() : 32479 nanoseconds
  convertInputStreamToStringNioWithCharset() : 32478 nanoseconds
  convertInputStreamToStringNioWithCharset() : 33048 nanoseconds
  convertInputStreamToStringNioWithCharset() : 32479 nanoseconds
  convertInputStreamToStringNioWithCharset() : 31909 nanoseconds
  convertInputStreamToStringNioWithCharset() : 32479 nanoseconds
  convertInputStreamToStringNioWithCharset() : 31909 nanoseconds

初回やたまに出てくる異常値を捨てるため、中央値(と言ってもいいのかわからんけど)を取るようにした。 今回は100回実行した結果から30回分の結果を取得し、その平均を求めることにした。 (これ中央値って言っていいのか…?統計詳しい人のツッコミが欲しい) (COUNT = 100, Median_SAMPLE_NUM = 30, RESULT_TIME_UNIT = TimeUnit.NANOSECONDS)

Result:
    (個別の結果は省略)
  convertInputStreamToString() (Median 30/100) : 43058 nanoseconds
    (個別の結果は省略)
  convertInputStreamToStringWithCharset() (Median 30/100) : 41026 nanoseconds
    (個別の結果は省略)
  convertInputStreamToStringNio() (Median 30/100) : 31871 nanoseconds
    (個別の結果は省略)
  convertInputStreamToStringNioWithCharset() (Median 30/100) : 34245 nanoseconds

この後3回程実行してみたが、charset指定のが速い時があった。 これはむしろ予測通りというか、Javaの内部エンコーディングUTF-16なのでUTF-16を指定すれば変換処理が実行されないと思ったからだ。 (そのためにUTF-16を指定した)

Result:
  convertInputStreamToString() (Median 30/100) : 43324 nanoseconds
  convertInputStreamToStringWithCharset() (Median 30/100) : 38272 nanoseconds
  convertInputStreamToStringNio() (Median 30/100) : 34568 nanoseconds
  convertInputStreamToStringNioWithCharset() (Median 30/100) : 32213 nanoseconds

Result:
  convertInputStreamToString() (Median 30/100) : 41709 nanoseconds
  convertInputStreamToStringWithCharset() (Median 30/100) : 42716 nanoseconds
  convertInputStreamToStringNio() (Median 30/100) : 34587 nanoseconds
  convertInputStreamToStringNioWithCharset() (Median 30/100) : 34473 nanoseconds

Result:
  convertInputStreamToString() (Median 30/100) : 40551 nanoseconds
  convertInputStreamToStringWithCharset() (Median 30/100) : 41500 nanoseconds
  convertInputStreamToStringNio() (Median 30/100) : 35461 nanoseconds
  convertInputStreamToStringNioWithCharset() (Median 30/100) : 34150 nanoseconds

ようやくベンチマーク環境が整ったのでちょっと興味があったallocateDirect()版も試してみた。 allocateDirect(int)はallocate(int)と違ってJVMのスタックにアロケーションする。 ただallocate(int)をallocateDirect(int)に単純に置き換えただけだと次の例外が飛んだ。

java.lang.UnsupportedOperationException
    at java.nio.ByteBuffer.array(ByteBuffer.java:994)
    at TestMain.convertInputStreamToStringNioDirectBuffer(TestMain.java:194)
    at TestMain$5.run(TestMain.java:52)
    at TestMain$Task.start(TestMain.java:93)
    at TestMain.main(TestMain.java:65)

該当箇所は以下の通り。

193:    while (0 <= (numRead = channel.read(buf))) {
194:        result.write(buf.array(), 0, numRead);
195:        buf.clear();
196:    }

どうやらbuf.array()allocateDirect()を使うと使えないらしい。 まぁ確かにスタックへのアクセスを許してしまったら良からぬことが起きそうではある。 あとこれも予想だけど、単にJavaのオブジェクト(byte[]オブジェクト)が用意されていないだけのような気もする。 ReadableByteChannel#hasArray()もfalseを返した。

そこで以下のように書き換えた。

static String convertInputStreamToStringNioDirectBuffer(InputStream is) throws IOException {
    ByteArrayOutputStream result = new ByteArrayOutputStream();
    ReadableByteChannel channel = Channels.newChannel(is);
    ByteBuffer buf = ByteBuffer.allocateDirect(BUFFER_SIZE);
    byte[] rawBuf = new byte[BUFFER_SIZE];
    int numRead;
    while (0 <= (numRead = channel.read(buf))) {
        buf.get(rawBuf, -buf.position(), numRead);
        result.write(rawBuf, 0, numRead);
        buf.clear();
    }
    return result.toString();
}

java.lang.IndexOutOfBoundsException
    at java.nio.Buffer.checkBounds(Buffer.java:567)
    at java.nio.DirectByteBuffer.get(DirectByteBuffer.java:265)
    at TestMain.convertInputStreamToStringNioDirectBuffer(TestMain.java:198)
    at TestMain$5.run(TestMain.java:52)
    at TestMain$Task.start(TestMain.java:94)
    at TestMain.main(TestMain.java:66)

がーん、だな…出鼻をくじかれた という事でByteBufferの使い方がまだよく分かってないことが分かったので、 気にせずもう少しNIOのコードを速度最適化することにした。 というのもByteBufferAPIを見ていたらByteBuffer#clear()は単純にpositionをリセットするのではなく、バッファの再割り当てを行うとか書いてあったのを目にしたから。

Buffer#clear() (Java Platform SE 6)

このバッファーをクリアします。バッファーの位置は 0、リミットは容量の値に設定されます。 マークは破棄されます。  

一連のチャネル読み込み操作または「put」操作を使用してこのバッファーにデータを格納する前に、このメソッドを呼び出します。例を示します。

     buf.clear();     // Prepare buffer for reading
     in.read(buf);    // Read data

このメソッドはバッファー内のデータを実際に消去するわけではありません。 しかし、そうした状況で使用されるため、クリア (clear) と命名されています。 

目にした…と思ったけどそうでもなかったかもしれない。 ただ確実にByteBuffer#reset()のが軽い処理(恐らくpositionの値しか変えない?)なはずだしそちらを使ってみる。 (以下にByteBuffer#reset()javadocを引用する)

Buffer#reset() (Java Platform SE 6)

バッファーの位置を以前にマークした位置に戻します。  
このメソッドを呼び出しても、マークの値は変更されません。 マークが破棄されることもありません。 

そして置き換えた後の結果がこちら。

Result:
  convertInputStreamToString() (Median 30/100) : 43400 nanoseconds
  convertInputStreamToStringWithCharset() (Median 30/100) : 40285 nanoseconds
  convertInputStreamToStringNio() (Median 30/100) : 35365 nanoseconds
  convertInputStreamToStringNioWithCharset() (Median 30/100) : 35480 nanoseconds

Result:
  convertInputStreamToString() (Median 30/100) : 42260 nanoseconds
  convertInputStreamToStringWithCharset() (Median 30/100) : 40779 nanoseconds
  convertInputStreamToStringNio() (Median 30/100) : 35822 nanoseconds
  convertInputStreamToStringNioWithCharset() (Median 30/100) : 35252 nanoseconds

Result:
  convertInputStreamToString() (Median 30/100) : 43077 nanoseconds
  convertInputStreamToStringWithCharset() (Median 30/100) : 40760 nanoseconds
  convertInputStreamToStringNio() (Median 30/100) : 35555 nanoseconds
  convertInputStreamToStringNioWithCharset() (Median 30/100) : 35100 nanoseconds

そしてByteBuffer#reset()を知ったので先ほど例外が出ていた

  • convertInputStreamToStringNioDirectBuffer()
  • convertInputStreamToStringNioDirectBufferWithCharset()

をちゃんと実装する事ができた。

static String convertInputStreamToStringNioDirectBuffer(InputStream is) throws IOException {
    ByteArrayOutputStream result = new ByteArrayOutputStream();
    ReadableByteChannel channel = Channels.newChannel(is);
    ByteBuffer buf = ByteBuffer.allocateDirect(BUFFER_SIZE);
    buf.mark();
    byte[] rawBuf = new byte[BUFFER_SIZE];
    int numRead;
    while (0 <= (numRead = channel.read(buf))) {
        buf.reset();
        buf.get(rawBuf, 0, numRead);
        result.write(rawBuf, 0, numRead);
        buf.reset();
    }
    return result.toString();
}
Result:
  convertInputStreamToString() (Median 30/100) : 42811 nanoseconds
  convertInputStreamToStringWithCharset() (Median 30/100) : 41121 nanoseconds
  convertInputStreamToStringNio() (Median 30/100) : 36258 nanoseconds
  convertInputStreamToStringNioWithCharset() (Median 30/100) : 36126 nanoseconds
  convertInputStreamToStringNioDirectBuffer() (Median 30/100) : 45299 nanoseconds
  convertInputStreamToStringNioDirectBufferWithCharset() (Median 30/100) : 45375 nanoseconds

Result:
  convertInputStreamToString() (Median 30/100) : 42545 nanoseconds
  convertInputStreamToStringWithCharset() (Median 30/100) : 40247 nanoseconds
  convertInputStreamToStringNio() (Median 30/100) : 35423 nanoseconds
  convertInputStreamToStringNioWithCharset() (Median 30/100) : 34986 nanoseconds
  convertInputStreamToStringNioDirectBuffer() (Median 30/100) : 43989 nanoseconds
  convertInputStreamToStringNioDirectBufferWithCharset() (Median 30/100) : 45603 nanoseconds

Result:
  convertInputStreamToString() (Median 30/100) : 43343 nanoseconds
  convertInputStreamToStringWithCharset() (Median 30/100) : 41216 nanoseconds
  convertInputStreamToStringNio() (Median 30/100) : 35727 nanoseconds
  convertInputStreamToStringNioWithCharset() (Median 30/100) : 35233 nanoseconds
  convertInputStreamToStringNioDirectBuffer() (Median 30/100) : 45869 nanoseconds
  convertInputStreamToStringNioDirectBufferWithCharset() (Median 30/100) : 45755 nanoseconds

メソッド名がObjective-C++よろしくエターナルフォースブリザード並に長くなっているけど気にしないことにする。 で、肝心の結果がallocateDirect()の場合遅くなっている。 これはまぁ書いている時に当然だろうと思っていたことで、ByteBuffer#array()が使えないから 一旦同じサイズのbyte[]オブジェクトにコピーした後さらにByteArrayOutputStreamにwrite()しているため。 ByteArrayOutputStreamに直接write()できればいいが、そういったAPIはないのでどうしようもない。 InputStream→ByteBuffer(Cスタック)→byte[]オブジェクト→ByteArrayOutputStream→Stringじゃ遅いに決まってる。 (もちろん実装クラスによっては間にもっと色々あるだろうけど)

結論とか

  • やっぱNIO速い
  • ベンチマークでは異常値に惑わされないことが重要
    • ほかにも重要なことは山ほどあるけど自分もわかっていないので参考リンクを見てください
  • 今回は単に「InputStream→Stringの変換」という事だったけど、InputStreamの実装クラスが決まればもっと速度最適化できるはず
    • 例えばネットワーク/HDD/メモリから読み込む際に全て同じバッファーサイズを取るのはおかしい。動的に決めたり、上位から指定したり、色々やれることがあるはず
  • あと関係ないけどEclipseってMarkdown Editorついてたのか…GitHub Fravoredじゃないので```でのコードブロックとか見れなかったけど

参考リンク

ベンチマーク用コード

gist.github.com