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

Performance golang中解析日期和时间的最佳方法,performance,parsing,date,time,go,Performance,Parsing,Date,Time,Go,我有很多datetime值作为字符串输入到我的golang程序中。 格式以位数固定: 2006/01/02 15:04:05 我开始用函数解析这些日期 但我的节目有一些表演问题。因此,考虑到我的格式是固定的,我试图通过编写自己的解析函数来调整它: func ParseDate2(strdate string) (time.Time, error) { year, _ := strconv.Atoi(strdate[:4]) month, _ := strconv.Atoi(st

我有很多datetime值作为字符串输入到我的golang程序中。 格式以位数固定:

2006/01/02 15:04:05
我开始用函数解析这些日期

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

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
}
最后,我在这两个函数之上做了一个基准测试,得到了以下结果:

 BenchmarkParseDate1      5000000               343 ns/op
 BenchmarkParseDate2     10000000               248 ns/op
BenchmarkParseDate1  5000000           355 ns/op
BenchmarkParseDate2 10000000           278 ns/op
BenchmarkParseDate3 20000000            88 ns/op
这是27%的性能改进。
就性能而言,是否有更好的方法可以改进这种日期时间解析?

从您已经展示的内容来看,使用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
}
您可以通过在
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)
}
将年份转换为数字需要两步方法:

year := atoi2(strdate[0:2])*100 + atoi2(strdate[2:4])
这带来了额外的改进:

BenchmarkParseDate4 50000000            61 ns/op

请注意,@peterSO建议的内联版本只稍微快一点(在我的例子中是54 ns/op),但上面的解决方案为您提供了错误检查的可能性,而内联版本会盲目地将所有字符转换为日期。

我希望使您的整个程序更快。例如,
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
}
基准:

$ 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
$ 
参考:


如果坚持使用
日期字符串
,请使用
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
}

需要解析多少个日期?我通过zmq推/拉接收实时日志。这是一条连续不断的河流。我批量记录行以限制事务的数量。主程序在峰值时间以5000 TPS的速度记录。在正常情况下是500 TPS。如果您需要读取数据,您可以将日期存储为字符串,然后将其转换为实际日期吗?如果这是对某类仪表板的实时更新,您真的需要每秒更新仪表板5000次吗?我想生成具有Max、Min、Average的实时图。如果可能的话,我还计划多做一些数学题。因此,如果可能的话,我希望避免任何数据丢失。但我理解你的观点,我需要检查在大量输入的情况下,删除部分指标是否会影响我的数字。“我想生成具有最大值、最小值、平均值的实时图。”。我也想要很多东西!你的行李在哪里。如果钱不是问题,那就买一台更大的电脑。否则,现实一点。该死,我是出于好奇才开始做这件事的,但我没有达到45纳秒/秒。我想我学会了不要想太复杂:)谢谢!在我的基准测试中,我没有得到相同的值:BenchmarkParseData1 5000000 331 ns/op BenchmarkParseData2 10000000 256 ns/op BenchmarkParseData3 20000000 143 ns/op,但它给出了更好的结果。Thanks@dbenque:这是因为您使用的是
字符串
而不是
[]字节
,因此存在转换成本。对日期字符串使用
ParseDate4
。看我修改过的答案。您现在的结果是什么?BenchmarkParseData1 5000000 328 ns/op BenchmarkParseData2 10000000 242 ns/op BenchmarkParseData3 20000000 135 ns/op BenchmarkParseData4 50000000 52.3 ns/op。。。。再次感谢。上面公式中的第一个字符应该乘以100,而不是10(
int(日期[0])-'0')*100
)@dbenque我还鼓励您在将其应用到生产中之前确保输入经过验证(这将花费您一些纳秒;-))额外纳秒的重要性取决于用例,但我更喜欢此解决方案,因为它清晰易读,而且速度快。
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
}