Scala 3でimplicitlyとsummonのcompile速度の違いを調べてみた

Scala 2の標準ライブラリには、大昔から以下のようなimplicitlyという、初心者にとっては一見謎なメソッドが存在します。

def implicitly[T](implicit e: T): T = e

https://github.com/scala/scala/blob/3548afcd51d3a60365f8c068d54a8d95c2e45b04/src/library/scala/Predef.scala#L218

これの話。

Scala 2の標準ライブラリはScala 3でも共通なので、implicitlyはScala 3からも使用可能です。

なんでこんなものがあるのか?というと、以下の説明がある程度関係しますが

xuwei-k.hatenablog.com

implicitというものを型クラスとして考えるなら?型クラスとして捉えなくても?基本的に implicit なもの(Scala 3だとgivenなもの)は、定義時も参照時も、名前指定で参照するべきではない、けど、スコープにあるものを取りたい、という場合に型だけを指定して取得するためのメソッドです。

さて、Scala 3には implicitly と一見ほぼ同じだけれど、微妙に違う summon というメソッドがあります。

https://github.com/scala/scala3/blob/977232cf3d919ccb6bf7c3fb93eda4b9241dac53/library/src/scala/runtime/stdLibPatches/Predef.scala#L37

transparent inline def summon[T](using x: T): x.type = x

inline なので、実行時にはメソッド呼び出しにならず、compile時に埋め込まれます。

また transparent もついて x.type になっているので、以下のように型も違いが出ます。

わざわざ微妙に別のものを用意したのは

  • それらの細かい違いをあえてつけたかった
  • 理想的なScala 3だけの世界においては implicit という言葉は撲滅させたいので implicitly という名前は嫌だったので、ほぼ同じだが別名で用意した

の2通りが考えられます。どちらの理由が強くて作ったのか?は歴史を調べてないので知りません。そこの詳細は今回の主題ではないので割愛。

さて、型が変わる挙動の違いについては、大抵問題にならないというか、どちらでもいいとはいえ、細かい違いがあるの、両方使い分けるの???みたいな気持ちになって悩ましいですね。

また、JVMの実行時の最適化が効けばおそらく違いは出ませんが implicitly の方は、(最適化のオプション指定しない限り?)メソッド呼び出しが残る、(普通はprimitiveをそのままimplicitにしないと思うがprimitiveの場合)boxingに違いが出るかも?、といった細かい違いもあります。

さて、それはそれとして

transparent inline って普通のメソッド呼び出しよりもcompile時に少し特殊な処理が必要だから、若干compile速度に影響が出るのでは???

という直感があり、以下で多少真面目にベンチマークを取ってみました。

Scalaは3.6.4です。その他詳細な条件は実際の以下のコード見てください。

https://github.com/xuwei-k/implicitly-summon-compile-time-benchmark/commit/9e7babad5b8535fac7f188447a55113ddd3a990e

それぞれを100万箇所も呼び出して、10回くらい繰り返しcompileをしてみた結果が以下です。 単位は秒

summon

94
79
75
78
77
76
77
76
79
76

implicitly

62
48
44
44
44
45
43
47
44
45

JVMの温まり考慮して雑に初回だけ除くとして

  • summon: 平均77.0秒。中央値77秒
  • implicitly: 平均44.9秒。中央値44秒

となります。summonの方がcompile速度が1.7倍くらい遅いです。

悲しいことに予想が当たってしまった・・・。

とはいえ、数十倍以上の差が出るならともかく、1.7倍程度なら許容範囲というか気にする必要はほぼないですかねぇ・・・???

もう少し別のパターンというか計測方法で実験したら、多少違う結果が出るかもしれませんが。