Go で文字列をフォーマットせずに出力する時普段は fmt.Print
を使っています.
Print("hello")
ですが,fmt.Print
は引き数に interface{}
を取るので string
を直接第1引数に取れる fmt.Printf
のほうが速いのでは…とふと思いました.
Printf("hello")
というわけでベンチマークをとってみました.
package main
import (
"fmt"
"io/ioutil"
"testing"
)
func BenchmarkPrint(b *testing.B) {
for i := 0; i < b.N; i++ {
fmt.Fprint(ioutil.Discard, "hello, hello")
}
}
func BenchmarkPrintf(b *testing.B) {
for i := 0; i < b.N; i++ {
fmt.Fprintf(ioutil.Discard, "hello, hello")
}
}
結果:
BenchmarkPrint-8 20000000 92.4 ns/op
BenchmarkPrintf-8 30000000 46.8 ns/op
PASS
ok command-line-arguments 3.402s
呼び出しコストは Print
のほうが2倍ぐらい大きいようです.では Printf
が良いのでしょうか?もうちょっと見てみます.
大きい文字列を入力にしてみます.
package main
import (
"fmt"
"io/ioutil"
"strings"
"testing"
)
var input = strings.Repeat("hello, ", 1000)
func BenchmarkPrint(b *testing.B) {
for i := 0; i < b.N; i++ {
fmt.Fprint(ioutil.Discard, input)
}
}
func BenchmarkPrintf(b *testing.B) {
for i := 0; i < b.N; i++ {
fmt.Fprintf(ioutil.Discard, input)
}
}
結果:
BenchmarkPrint-8 10000000 167 ns/op
BenchmarkPrintf-8 500000 2892 ns/op
PASS
ok command-line-arguments 3.331s
今度は Printf
のほうがかなり遅い結果になりました.Printf
はフォーマット文字列を走査しないといけないからでしょうか?しかしフォーマット文字列の後の可変長部分は空なのでそれは不要のはずです.おかしいですね…
と思ったら,どうやら可変長部分が空でもフォーマット文字列を走査していました.それは遅い…
結論
- 小さい文字列を繰り返し print するときは
Printf
が良く,大きめの文字列を一度に print するときはPrint
が良く,それ以外の場合はどちらでも良い -
Print
の呼び出しコストは高々2倍なので,よほどのことが無い限り当初通りPrint
を使っておけば良い - Go はベンチマーク取りやすくて良い