为什么在“热身”后解组JSON的速度更快?

时间:2018-08-23 16:44:34

标签: json performance go optimization

我注意到,当我在Go HTTP服务器的上下文中对JSON进行编组时,即使是小对象也要花费30,000+纳秒。这对我来说似乎很大,所以我运行了一些孤立的基准测试,令人惊讶地显示每个非海警的平均时间约为500纳米。为了进一步研究这个问题,我编写了一个程序,该程序只对同一小对象执行一系列的解组,这表明第一个解组速度很慢,而随后的解组速度更快:

package main

import (
    "time"
    "fmt"
    "encoding/json"
)

var b []byte

type Dog struct {
    Age int `json:"Age"`
}

func unmarshalDog() {
    dogCopy := Dog{}

    start := time.Now().UnixNano()
    json.Unmarshal(b, &dogCopy)
    end := time.Now().UnixNano()
    fmt.Printf("Time to marshal/unmarshal: %d\n", end-start)
}

func main() {
    // Marshal an object into a byte array which we will repeatedly unmarshal from
    d := Dog {
        Age: 5,
    }

    var err error
    b, err = json.Marshal(d)
    if err != nil {
        panic(err)
    }

    for i := 0; i < 20; i++ {
        unmarshalDog()
    }

    // Allow the goroutines to finish before terminating execution.
    time.Sleep(3 * time.Second)
}

输出:

$ go run testJSONPerformance.go
Time to marshal/unmarshal: 34127
Time to marshal/unmarshal: 1465
Time to marshal/unmarshal: 979
Time to marshal/unmarshal: 892
Time to marshal/unmarshal: 849
Time to marshal/unmarshal: 814
Time to marshal/unmarshal: 822
Time to marshal/unmarshal: 822
Time to marshal/unmarshal: 815
Time to marshal/unmarshal: 829
Time to marshal/unmarshal: 822
Time to marshal/unmarshal: 819

更有趣的是,当我运行相同的程序但通过unmarshalDog()在单独的goroutine中运行对go unmarshalDog()的每次调用时,热身现象消失了,平均解组时间要高得多:

Time to marshal/unmarshal: 36540
Time to marshal/unmarshal: 4652
Time to marshal/unmarshal: 56959
Time to marshal/unmarshal: 3887
Time to marshal/unmarshal: 57068
Time to marshal/unmarshal: 3519
Time to marshal/unmarshal: 37160

我还尝试了一个版本,而不是封送相同的字节数组,而是封送和封送不同的对象,以防出现某种运行时缓存的情况。在这种情况下,结果是相同的。

我很想知道这种明显的“热身”是怎么回事。在HTTP服务器的上下文中,每个请求获得一个不同的goroutine,因此每个解组平均速度非常慢。考虑到Go在某些情况下有可能在1/50的时间内进行解组,这似乎不是最佳选择。感谢所有直觉!

1 个答案:

答案 0 :(得分:1)

JSON解组器在任何类型的第一次解组时缓存信息。此类型的后续解组使用此缓存的信息。

缓存在goroutine之间共享,但是没有代码可确保只有一个goroutine尝试创建第一个缓存值。一旦一个goroutine将值存储到缓存中,所有新用户将使用该缓存的值。

用于缓存的代码为here