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

标签 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)的两倍吗?

最佳答案

是的,有可能。当输入的字符串长度小于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
}

关于performance - 为什么与strconv.Atoi相比,strconv.ParseUint这么慢?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64513411/

相关文章:

c# - 是否有将 "YYYYMMDDHHMMSS.UUUUUU-TZO"格式转换为 C# DateTime 的 API?

c - Go中的递归链表类型别名

c++ - 无法匹配重载的运算符

检查一种整数类型的值是否适合另一种整数类型

java - 如何确定看起来像这样的大 O : (x -1) + (x - 2) + (x - 3) . .. (x - x)

Golang 推断接口(interface)

go - 将 interface{} 转换为字符串数组

javascript - 出于性能原因,我应该避免多次执行相同的函数声明吗?

mysql - 数据库索引用于同一组列的多个查询组合?

c# - 从 Texture2D 创建 UI 图像