Golang - golang 内存配置文件如何计算分配/操作?
我正在编写一个自定义 JSON marshal 函数,并将其与内置 json.Marshal 方法进行比较。
我的理解是,当 bytes.Buffer
达到其容量时,它需要将其大小加倍,这会花费 1 次分配。
然而,基准测试结果似乎表明 json.Marshal 正在以一种不需要在增加底层缓冲区时进行分配的方式来执行此操作,而我的实现似乎每次缓冲区加倍时都会花费额外的分配。
为什么MarshalCustom
(下面的代码)需要分配比json.Marshal
更多的空间?
$ go test -benchmem -run=^$ -bench ^BenchmarkMarshalText$ test
BenchmarkMarshalText/Marshal_JSON-10 79623 13545 ns/op 3123 B/op 2 allocs/op
BenchmarkMarshalText/Marshal_Custom-10 142296 8378 ns/op 12464 B/op 8 allocs/op
PASS
ok test 2.356s
完整代码。
type fakeStruct struct {
Names []string `json:"names"`
}
var ResultBytes []byte
func BenchmarkMarshalText(b *testing.B) {
names := randomNames(1000)
b.Run("Marshal JSON", func(b *testing.B) {
fs := fakeStruct{
Names: names,
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
ResultBytes, _ = json.Marshal(fs)
}
})
b.Run("Marshal Custom", func(b *testing.B) {
fs := fakeStruct{
Names: names,
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
ResultBytes = MarshalCustom(fs)
}
})
}
func MarshalCustom(fs fakeStruct) []byte {
var b bytes.Buffer
b.WriteByte('{')
// Names
b.WriteString(`,"names":[`)
for i := 0; i < len(fs.Names); i++ {
if i > 0 {
b.WriteByte(',')
}
b.WriteByte('"')
b.WriteString(fs.Names[i])
b.WriteByte('"')
}
b.WriteByte(']')
b.WriteByte('}')
buf := append([]byte(nil), b.Bytes()...)
return buf
}
func randomNames(num int) []string {
const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
const maxLen = 5
rand.Seed(time.Now().UnixNano())
res := make([]string, rand.Intn(num))
for i := range res {
l := rand.Intn(maxLen) + 1 // cannot be empty
s := make([]byte, l)
for j := range s {
s[j] = letters[rand.Intn(len(letters))]
}
res[i] = string(s)
}
return res
}
I'm writing a custom JSON marshal function and comparing it to the built-in json.Marshal
method.
My understanding is that when bytes.Buffer
reaches its capacity, it needs to double its size and this costs 1 allocation.
However, benchmark result seems to indicate json.Marshal
is doing this in a way where it does NOT need to allocate whenever it grows the underlying buffer, whereas my implementation seems to cost an extra allocation everytime the buffer doubles.
Why would MarshalCustom
(code below) need to allocate more than json.Marshal
?
$ go test -benchmem -run=^$ -bench ^BenchmarkMarshalText$ test
BenchmarkMarshalText/Marshal_JSON-10 79623 13545 ns/op 3123 B/op 2 allocs/op
BenchmarkMarshalText/Marshal_Custom-10 142296 8378 ns/op 12464 B/op 8 allocs/op
PASS
ok test 2.356s
Full code.
type fakeStruct struct {
Names []string `json:"names"`
}
var ResultBytes []byte
func BenchmarkMarshalText(b *testing.B) {
names := randomNames(1000)
b.Run("Marshal JSON", func(b *testing.B) {
fs := fakeStruct{
Names: names,
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
ResultBytes, _ = json.Marshal(fs)
}
})
b.Run("Marshal Custom", func(b *testing.B) {
fs := fakeStruct{
Names: names,
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
ResultBytes = MarshalCustom(fs)
}
})
}
func MarshalCustom(fs fakeStruct) []byte {
var b bytes.Buffer
b.WriteByte('{')
// Names
b.WriteString(`,"names":[`)
for i := 0; i < len(fs.Names); i++ {
if i > 0 {
b.WriteByte(',')
}
b.WriteByte('"')
b.WriteString(fs.Names[i])
b.WriteByte('"')
}
b.WriteByte(']')
b.WriteByte('}')
buf := append([]byte(nil), b.Bytes()...)
return buf
}
func randomNames(num int) []string {
const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
const maxLen = 5
rand.Seed(time.Now().UnixNano())
res := make([]string, rand.Intn(num))
for i := range res {
l := rand.Intn(maxLen) + 1 // cannot be empty
s := make([]byte, l)
for j := range s {
s[j] = letters[rand.Intn(len(letters))]
}
res[i] = string(s)
}
return res
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
@oakad 是正确的。如果我在基准测试的每次迭代中强制运行 GC,则分配/操作会更接近甚至相同。
@oakad is correct. If I force a GC run in every iteration of the benchmark, the allocs/op is much closer or even the same.