Rows.Scan() に渡す変数はループ外で宣言した方が速い

go-mysql-driver のアロケーションを調査していて気づいた小ネタ。

--- a/benchmark_test.go
+++ b/benchmark_test.go
@@ -423,9 +423,9 @@ func BenchmarkReceiveMassiveRows(b *testing.B) {
                                b.Errorf("failed to select: %v", err)
                                return
                        }
+                       var i int
+                       var s sql.RawBytes
                        for rows.Next() {
-                               var i int
-                               var s sql.RawBytes
                                err = rows.Scan(&i, &s)
                                if err != nil {
                                        b.Errorf("failed to scan: %v", err)

こう言うふうに、Scanに渡す変数は rows.Next() ループの外で宣言した方がいい。

rows.Scan() に &i のようにアドレスを渡しているが、Scan() に渡された変数はエスケープしていると判断される。 C言語などではこのアドレスがScan()を呼び出した後に利用されないことをプログラマが保証するのでコンパイラはスタックに変数を宣言できるが、Goではエスケープするならヒープアロケーションしてしまう。

ループの外で宣言することで、ヒープアロケーションをループごとではなく1度だけに限定できる。

このブログに乗せているコードは引用を除き CC0 1.0 で提供します。