相変わらず、noboxというライブラリを作ってる途中で気づいた、(普通は気にする必要ない)細かいパフォーマンスの話です。
気づいた経緯は以下のような感じ
URL withFilter実装してみたけど、withFilterしてmapするより、filterしてmapしたほうがなぜか速くてつらい・・・。(何回か同じようなことあったけど)こういうの原因探るの難しそうだしなぁ・・・
2013-11-05 11:48:05 via web
List(1).collect{case a => a}を -Xprint:jvm したら"final override def applyOrElse(x1: Int, default: Function1): Object"となっていて、boxingされてる疑惑
2013-11-05 12:04:31 via web to @xuwei_k
自分の仮説が正しいなら、withFilterのほうは改善方法あるけど、普通のcollectのほうはboxingを防ぐ方法がなくて詰んだ。PartialFunctionをリテラルで受け取ると、本体がapplyOrElseに実装されるから、その戻り値のboxingを防ぐのが不可能では
2013-11-05 12:24:51 via web
というわけで、「withFilterしてmap」という処理を、最初は単にPartialFunction作って(自分が実装した)collectを呼んでいたのですが、それをやめてPartialFunction使わないようにしたら、無事「filterしてmap*1」するよりも2倍程度は速くなりました。
そして、そのwithFilterの実装を入れた、v0.1.5をreleaseしておきました。
https://bintray.com/xuwei-k/maven/nobox/0.1.5
https://github.com/xuwei-k/nobox/tree/v0.1.5
相変わらずグラフを見ると、誰も使っていない感じですが*2
https://bintray.com/xuwei-k/maven/nobox?from=statistics
使ってもらうことより、実装していくことにより勉強するのが目的なので、まぁ飽きるまで続けます。
といっても、そろそろ簡単に改善できる部分はなくなってきているのですが・・・。
さて、noboxのライブラリの説明ばかりで、タイトルの件の説明してなかったので、ここから説明します。versionはScala2.10.3とScala2.11.0-M6時点です。*3
ポイントを箇条書きにすると
- Scala2.10からapplyOrElseというメソッドがPartialFunctionに追加されてる
- PartialFunctionのapplyは、デフォルトではapplyOrElseを内部的に呼ぶ
- AbstructPartialFunctionにはspecializedアノテーションがついている
- しかし、
def applyOrElse[A1 <: A, B1 >: B](x: A1, default: A1 => B1): B1
というシグネチャのため、type erasure後は、B1はプリミティブ型になれない(java.lang.Objectになる) - ちなみに、
B1 >: B
となっていること自体は*4とても正しい
という理由です。*5
*6
ところで、Scala2.9以前だとAbstractPartialFunctionもないしapplyOrElseもないので、違う結果になるかもしれませんが、まだ試してません。追記
以下のようなコードで簡単なベンチマークとったら
Scala2.9のほうがScala2.10より速い結果がでた・・・。
この微妙に遅い(?)というデメリットを打ち消すほどに、「Scala2.10からapplyOrElseを新規に追加し、applyからapplyOrElseを呼ぶという実装にした」のはメリットがあったのか謎・・・。applyOrElseを追加するにしても、さらにもう少し工夫してboxing防ぐことはできなかったのか・・・?
*1:つまり、filterした後に、一旦中間オブジェクトが生成される
*2:グラフ中でいくつかDownloadがあるのは、ちゃんとファイルがuploadされてるか確かめるために、自分がdownloadしたもんがほとんど
*3:2.10.3と2.11.0-M6で、このあたりにほぼ違いは無さそう
*4:パフォーマンス面を除けば
*5:もしこの推測間違ってたらだれか教えてください・・・。-Xprint:jvmした結果を元に予想しただけで、javapしたり、その他プロファイラを使って確かめたわけではありません。
*6:明示的にnew PartialFunctionとインスタンス生成して、applyをoverrideする場合はboxingされません。が、ほとんどの場合PartialFunctionはそんな使い方しませんし、よっぽどのことが無い限り、boxing避けるためにnew PartialFunctionと書いてapplyをoverrideするのは馬鹿げています