在golang中解析日期和时间的最佳方法

dbe*_*que 6 performance time parsing date go

我有很多日期时间值作为字符串传入我的golang程序.格式以位数固定:

2006/01/02 15:04:05
Run Code Online (Sandbox Code Playgroud)

我开始用time.Parse函数解析这些日期

const dtFormat = "2006/01/02 15:04:05"

func ParseDate1(strdate string) (time.Time, error) {
    return time.Parse(dtFormat, strdate)
}
Run Code Online (Sandbox Code Playgroud)

但我的节目有一些表演问题.因此,我尝试通过编写自己的解析函数来调整它,考虑到我的格式是固定的:

func ParseDate2(strdate string) (time.Time, error) {
    year, _ := strconv.Atoi(strdate[:4])
    month, _ := strconv.Atoi(strdate[5:7])
    day, _ := strconv.Atoi(strdate[8:10])
    hour, _ := strconv.Atoi(strdate[11:13])
    minute, _ := strconv.Atoi(strdate[14:16])
    second, _ := strconv.Atoi(strdate[17:19])

    return time.Date(year, time.Month(month), day, hour, minute, second, 0, time.UTC), nil
}
Run Code Online (Sandbox Code Playgroud)

最后我在这两个函数的基础上做了一个基准测试,得到了以下结果:

 BenchmarkParseDate1      5000000               343 ns/op
 BenchmarkParseDate2     10000000               248 ns/op
Run Code Online (Sandbox Code Playgroud)

这是性能提升了27%.在性能方面是否有更好的方法可以改善这种日期时间解析?

tom*_*asz 6

根据您已经展示的内容,使用strconv.Atoi直接改善您的表现.您可以进一步推送它并atoi为您的特定用例推送自己的.

您希望每个项目都是正数10.您还知道它不会溢出,因为传递的字符串表示的最大长度为4.唯一可能的错误是字符串中的非数字字符.知道了这一点,我们可以简单地做到以下几点:

var atoiError = errors.New("invalid number")
func atoi(s string) (x int, err error) {
    i := 0
    for ; i < len(s); i++ {
        c := s[i]
        if c < '0' || c > '9' {
            err = atoiError
            return
        }
        x = x*10 + int(c) - '0'
    }
    return
}
Run Code Online (Sandbox Code Playgroud)

把它包装成ParseDate3,我得到以下结果:

BenchmarkParseDate1  5000000           355 ns/op
BenchmarkParseDate2 10000000           278 ns/op
BenchmarkParseDate3 20000000            88 ns/op
Run Code Online (Sandbox Code Playgroud)

你可以通过不返回错误来加快速度atoi,但我鼓励你无论如何都要测试输入(除非你的代码中的其他地方验证了它).

在看到内联解决方案后,替代atoi方法:

进一步推动这一点,你可以利用这样一个事实:除了一个传递的字符串之外的所有字符串都是2位数长(年份是4位数,但它是2的乘法).使用2位数字符串创建atoi将消除for循环.例:

// Converts string of 2 characters into a positive integer, returns -1 on error
func atoi2(s string) int {
    x := uint(s[0]) - uint('0')
    y := uint(s[1]) - uint('0')
    if x > 9 || y > 9 {
        return -1 // error
    }
    return int(x*10 + y)
}
Run Code Online (Sandbox Code Playgroud)

将年份转换为数字需要两步法:

year := atoi2(strdate[0:2])*100 + atoi2(strdate[2:4])
Run Code Online (Sandbox Code Playgroud)

这提供了额外的改进:

BenchmarkParseDate4 50000000            61 ns/op
Run Code Online (Sandbox Code Playgroud)

请注意,@ peterSO提出的内联版本仅稍微快一点(在我的情况下为54 ns/op),但上面的解决方案可以让您进行错误检查,而内联版本会盲目地将所有字符转换为日期.


pet*_*rSO 5

我希望能让你的整个程序更快。例如,ParseDate3

func ParseDate3(date []byte) (time.Time, error) {
    year := (((int(date[0])-'0')*10+int(date[1])-'0')*10+int(date[2])-'0')*10 + int(date[3]) - '0'
    month := time.Month((int(date[5])-'0')*10 + int(date[6]) - '0')
    day := (int(date[8])-'0')*10 + int(date[9]) - '0'
    hour := (int(date[11])-'0')*10 + int(date[12]) - '0'
    minute := (int(date[14])-'0')*10 + int(date[15]) - '0'
    second := (int(date[17])-'0')*10 + int(date[18]) - '0'
    return time.Date(year, month, day, hour, minute, second, 0, time.UTC), nil
}
Run Code Online (Sandbox Code Playgroud)

基准:

$ go test -bench=.
testing: warning: no tests to run
PASS
BenchmarkParseDate1  5000000           308 ns/op
BenchmarkParseDate2 10000000           225 ns/op
BenchmarkParseDate3 30000000            44.9 ns/op
ok      so/test 5.741s
$ go test -bench=.
testing: warning: no tests to run
PASS
BenchmarkParseDate1  5000000           308 ns/op
BenchmarkParseDate2 10000000           226 ns/op
BenchmarkParseDate3 30000000            45.4 ns/op
ok      so/test 5.757s
$ go test -bench=.
testing: warning: no tests to run
PASS
BenchmarkParseDate1  5000000           312 ns/op
BenchmarkParseDate2 10000000           225 ns/op
BenchmarkParseDate3 30000000            45.0 ns/op
ok      so/test 5.761s
$ 
Run Code Online (Sandbox Code Playgroud)

参考:

分析 Go 程序


如果你坚持用date string,就用ParseDate4

func ParseDate4(date string) (time.Time, error) {
    year := (((int(date[0])-'0')*10+int(date[1])-'0')*10+int(date[2])-'0')*10 + int(date[3]) - '0'
    month := time.Month((int(date[5])-'0')*10 + int(date[6]) - '0')
    day := (int(date[8])-'0')*10 + int(date[9]) - '0'
    hour := (int(date[11])-'0')*10 + int(date[12]) - '0'
    minute := (int(date[14])-'0')*10 + int(date[15]) - '0'
    second := (int(date[17])-'0')*10 + int(date[18]) - '0'
    return time.Date(year, month, day, hour, minute, second, 0, time.UTC), nil
}
Run Code Online (Sandbox Code Playgroud)