背景
最近在開發的項目,大量用到了fmt.Sprintf去做int/uint32/float 轉string, 結果壓測的時候竟然發現fmt.Sprintf耗時較大,有性能問題。
分析
通過查看fmt.Sprintf源碼, 可以看出效率低有兩個原因:
fmt.Sprintf
接受的類型是 interface{},內部使用了反射。所以,與相應的標準庫函數相比,fmt.Sprintf
需要更大的開銷fmt.Sprintf
的用途是格式化字符串,需要去解析格式串,比如%s%d之類的
func Sprintf(format string, a ...interface{}) string {
p := newPrinter()
p.doPrintf(format, a)
s := string(p.buf)
p.free()
return s
}
func (p *pp) doPrintf(format string, a []interface{}) {
end := len(format)
argNum := 0 // we process one argument per non-trivial format
afterIndex := false // previous item in format was an index like [3].
p.reordered = false
formatLoop:
for i := 0; i < end; {
p.goodArgNum = true
lasti := i
for i < end && format[i] != '%' {
i++
}
if i > lasti {
p.buf.writeString(format[lasti:i])
}
if i >= end {
// done processing format string
break
}
// Process one verb
i++
// Do we have flags?
p.fmt.clearflags()
simpleFormat:
for ; i < end; i++ {
c := format[i]
switch c {
case '#':
p.fmt.sharp = true
case '0':
p.fmt.zero = !p.fmt.minus // Only allow zero padding to the left.
case '+':
p.fmt.plus = true
case '-':
p.fmt.minus = true
p.fmt.zero = false // Do not pad with zeros to the right.
case ' ':
p.fmt.space = true
default:
// Fast path for common case of ascii lower case simple verbs
// without precision or width or argument indices.
if 'a' <= c && c <= 'z' && argNum < len(a) {
if c == 'v' {
// Go syntax
p.fmt.sharpV = p.fmt.sharp
p.fmt.sharp = false
// Struct-field syntax
p.fmt.plusV = p.fmt.plus
p.fmt.plus = false
}
p.printArg(a[argNum], rune(c))
argNum++
i++
continue formatLoop
}
// Format is more complex than simple flags and a verb or is malformed.
break simpleFormat
}
}
結論
- 不要使用fmt.Sprintf去做類型轉換,更好的做法是使用標準庫函數strconv, 推薦使用
strconv.FormatInt
(int64
),對於int
類型,strconv.Itoa
對前者做了一個封裝。
參考
http://liyangliang.me/posts/2014/06/donnot-use-fmt-sprintf-for-type-conversion/