为什么 strconv.ParseUint 比 strconv.Atoi 慢?

Fre*_*ors 0 performance benchmarking type-conversion go microbenchmark

我正在对从stringtointuint使用以下代码的解组进行基准测试:

package main

import (
    "strconv"
    "testing"
)

func BenchmarkUnmarshalInt(b *testing.B) {
    for i := 0; i < b.N; i++ {
        UnmarshalInt("123456")
    }
}

func BenchmarkUnmarshalUint(b *testing.B) {
    for i := 0; i < b.N; i++ {
        UnmarshalUint("123456")
    }
}

func UnmarshalInt(v string) int {
    i, _ := strconv.Atoi(v)
    return i
}

func UnmarshalUint(v string) uint {
    i, _ := strconv.ParseUint(v, 10, 64)
    return uint(i)
}
Run Code Online (Sandbox Code Playgroud)

结果:

Running tool: C:\Go\bin\go.exe test -benchmem -run=^$ myBench/main -bench .

goos: windows
goarch: amd64
pkg: myBench/main
BenchmarkUnmarshalInt-8     99994166            11.7 ns/op         0 B/op          0 allocs/op
BenchmarkUnmarshalUint-8    54550413            21.0 ns/op         0 B/op          0 allocs/op
Run Code Online (Sandbox Code Playgroud)

有没有可能第二个 ( uint) 的速度几乎是第一个 ( int) 的两倍?

Pau*_*kin 7

是的,这是可能的。strconv.Atoi当输入字符串长度小于 19(或 10,如果int是 32 位)时,有一个快速路径。这允许它快得多,因为它不需要检查溢出。

如果您将测试编号更改为“1234567890123456789”(假设为 64 位 int),那么您的 int 基准测试比 uint 基准测试稍慢,因为无法使用快速路径。在我的机器上,签名版本需要 37.6 ns/op,而未签名版本需要 31.5 ns/op。

这是修改后的基准代码(注意我添加了一个总结解析结果的变量,以防编译器变得聪明并对其进行优化)。

package main

import (
        "fmt"
        "strconv"
        "testing"
)

const X = "1234567890123456789"

func BenchmarkUnmarshalInt(b *testing.B) {
        var T int
        for i := 0; i < b.N; i++ {
                T += UnmarshalInt(X)
        }
        fmt.Println(T)
}

func BenchmarkUnmarshalUint(b *testing.B) {
        var T uint
        for i := 0; i < b.N; i++ {
                T += UnmarshalUint(X)
        }
        fmt.Println(T)
}

func UnmarshalInt(v string) int {
        i, _ := strconv.Atoi(v)
        return i
}

func UnmarshalUint(v string) uint {
        i, _ := strconv.ParseUint(v, 10, 64)
        return uint(i)
}
Run Code Online (Sandbox Code Playgroud)

作为参考,strconv.Atoi目前标准库中的代码如下:

func Atoi(s string) (int, error) {
    const fnAtoi = "Atoi"

    sLen := len(s)
    if intSize == 32 && (0 < sLen && sLen < 10) ||
        intSize == 64 && (0 < sLen && sLen < 19) {
        // Fast path for small integers that fit int type.
        s0 := s
        if s[0] == '-' || s[0] == '+' {
            s = s[1:]
            if len(s) < 1 {
                return 0, &NumError{fnAtoi, s0, ErrSyntax}
            }
        }

        n := 0
        for _, ch := range []byte(s) {
            ch -= '0'
            if ch > 9 {
                return 0, &NumError{fnAtoi, s0, ErrSyntax}
            }
            n = n*10 + int(ch)
        }
        if s0[0] == '-' {
            n = -n
        }
        return n, nil
    }

    // Slow path for invalid, big, or underscored integers.
    i64, err := ParseInt(s, 10, 0)
    if nerr, ok := err.(*NumError); ok {
        nerr.Func = fnAtoi
    }
    return int(i64), err
}
Run Code Online (Sandbox Code Playgroud)