我注意到,当我在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的时间内进行解组,这似乎不是最佳选择。感谢所有直觉!
答案 0 :(得分:1)
JSON解组器在任何类型的第一次解组时缓存信息。此类型的后续解组使用此缓存的信息。
缓存在goroutine之间共享,但是没有代码可确保只有一个goroutine尝试创建第一个缓存值。一旦一个goroutine将值存储到缓存中,所有新用户将使用该缓存的值。
用于缓存的代码为here。