Golang の generics パフォーマンス
LuneScript は、 Golang (1.16 以降)へのトランスコンパイルを対応しています。 また、LuneScript は Generics に対応しています。
一方で、 Golang は version 1.18 から Generics に対応しています。
つまり、 LuneScript は Golang が Generics 対応する前から Generics を利用できていました。
では、 Generics を利用していた LuneScript のコードを どうやって Generics 対応前の Golang にトランスコンパイルしていたかというと、 Generics の型パラメータの値を interface{} に変換して処理を行なっていました。
Java でいうところの autoboxing のようなことを変換時にやっていた、 と思ってもらえば良いです。
で、 Golang ネイティブで Generics 対応されて autoboxing する必要がなくなったので、 LuneScript の Golang へのトランスコンパイルで Golang の Generics を利用するように変更する検討作業に入りました。
検討に利用する golang のバージョン
今回は以下の go のバージョンを利用して検討します。
$ go version go version go1.19.2 linux/amd64
Generics のパフォーマンス確認
既存の処理を変更するので、 それなりのメリットがないと意味がないです。
そのメリットとは、 autoboxing 相当の処理をやめて Golang ネイティブの Generics を利用することで、 多少なりとも処理が速くなるんじゃないか? ということです。
そのために、 次の処理を既存 autoboxing 処理と、 ネイティブの Generics 処理とで実行したパフォーマンスを比較します。
- 「LuneScript の
List<int>
の要素の合計を計算する。」
テストコード
具体的なコードは以下です。
このコードの GenList[V any]
が generics を利用した List<int> の構造で、
BoxingList
が autoboxing を利用している従来の List<int> の構造です。
それぞれの構造に 1000 個の int 要素を事前に追加しておき、 リストから値を取りだしてトータルを計算する処理を 1000000 回繰替えして、 その時間を計測します。
|
|
実行結果
実行結果が以下です。
|
|
これを見ると分かりますが、 なんと ネイティブの Generics を利用した方が倍も遅くなってしまいました。
これは意外でした。
効果がないどころか、逆に遅くなってしまいました。
なお、 このサンプルプログラムでは List の要素にアクセスする際、
定義したメソッド GetAt()
を介します。
このメソッドを通さずに直節メンバにアクセスするように変更したところ (コメントアウトしている箇所のコメントを外し、 その直前処理を代わりにコメントアウトする)、 次のように generics を利用した方が速く処理が終りました。
|
|
generics を利用したメソッドは、オーバーヘッドが異様に大きいという結果になりました。
ところで、 generics のメソッド対応方法って、これであってるよね??
|
|
まとめ
以上の結果をまとめると、次になります。
- generics を利用したメンバアクセスは、any との相互変換がなくなる分、速くなる。
- 但し generics を利用したメソッドのオーバーヘッドが大きい。
このことから、 LuneScript の autoboxing 処理をそのまま golang の generics へ 置き換えることはしません。
ですが、generics を利用した方が速くなるケースがあるのも事実なので、 今後も generics の検討を進めて、 効果的な適応方法が見つかったら対応を進めたいと思います。