为什么与strconv.Atoi相比,strconv.ParseUint这么慢?

时间:2020-10-24 12:41:22

标签: performance go type-conversion benchmarking microbenchmark

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

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)
}

结果:

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

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

1 个答案:

答案 0 :(得分:6)

是的,有可能。当输入字符串的长度小于19时strconv.Atoi具有快速路径(如果int为32位,则为10)。这样可以使其速度更快,因为它不需要检查溢出。

如果将测试号更改为“ 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)
}

作为参考,当前标准库中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
}