Predef.identityをプリミティブ型に対して使うと少し遅くなる(はず)

どうでもいい細かいパフォーマンスの話。とあるライブラリ作って遊んでる影響で、最近boxingや細かいパフォーマンスについて色々調べてるので、ついでに書いておく。
以前から、これちょっとだけ気になってたけど、ちゃんと計測してみたら一応差がでる。

つまり

val intList: List[Int] = // 大きなIntのList
intList.map(identity)

よりも

val intList: List[Int] = // 大きなIntのList
intList.map(x => x)

のほうが、速くなる(可能性がある)という話です。もちろん、最適化によって差がでないこともあるし、差が出るとしてもせいぜい数%から数十%程度でしょう。

scala -Xprint:jvm -e "List(0).map(identity)"

scala -Xprint:jvm -e "List(0).map(x => x)"

をしてみると、identityのほうは以下のようになります。(生成される関数オブジェクトの重要部分だけ抜粋。Scala2.10.3)

@SerialVersionUID(0) final <synthetic> class Main$$anon$1$$anonfun$1 extends runtime.AbstractFunction1$mcII$sp with Serializable {
  final def apply(x: Int): Int = Main$$anon$1$$anonfun$1.this.apply$mcII$sp(x);
  <specialized> def apply$mcII$sp(x: Int): Int = scala.Int.unbox(scala.this.Predef.identity(scala.Int.box(x)));
  final <bridge> def apply(v1: Object): Object = scala.Int.box(Main$$anon$1$$anonfun$1.this.apply(scala.Int.unbox(v1)));
}

List(0).map(identity)の場合、おそらく引数がObject型のapplyを経由してapply$mcII$spが呼ばれる*1はずなので、

  • applyからapply$mcII$spへ渡すときにunboxing
  • apply$mcII$spからPredef.identityへ渡すときにまたboxing
  • Predef.identityから返ってきた値を、apply$mcII$spがunboxingして返す
  • apply$mcII$spから返ってきた値をapplyがboxingして返す

と、boxingとunboxingがそれぞれ2回ずつ呼ばれてるはずです。おぉ・・・

一方

scala -Xprint:jvm -e "List(0).map(x => x)"

だと

@SerialVersionUID(0) final <synthetic> class Main$$anon$1$$anonfun$1 extends runtime.AbstractFunction1$mcII$sp with Serializable {
  final def apply(x: Int): Int = Main$$anon$1$$anonfun$1.this.apply$mcII$sp(x);
  <specialized> def apply$mcII$sp(x: Int): Int = x;
  final <bridge> def apply(v1: Object): Object = scala.Int.box(Main$$anon$1$$anonfun$1.this.apply(scala.Int.unbox(v1)));
};

となり、boxingとunboxingは1回ずつですね。あと、理論的には、Predef.identityの関数呼び出し1回分のオーバヘッドも減るはずです。*2


以上どうでもいい細かいパフォーマンスの話でした。
念のため言っておきますが、こんな細かいところを気にしても目に見えて速くなるわけではないし、こんなどうでもいいテクニックは普通の人は覚えなくていいです。

*1:Listのmapは、プリミティブ型それぞれに対して最適化されてないので。そもそもListに限らずほとんどが最適化されてないが

*2:そのオーバヘッドのことを考えれば、プリミティブに限らず遅くなるかもだが、大概はJITがinline化したりするかな