Go:使用指针进行JSON编码的结构体比使用副本慢吗?

huangapple go评论81阅读模式
英文:

Go: json encoding struct with pointers is slower than with copies?

问题

我有以下的测试代码:

package main

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

type Coll1 struct {
    A string
    B string
    C string
}

type Coll2 struct {
    A *string
    B *string
    C *string
}

var as = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
var bs = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
var cs = "ccccccccccccccccccccccccccccccccc"

func testBM1(b *testing.B) {
    for i := 0; i<b.N; i++ {
        json.Marshal(Coll1{as,bs,cs})
    }
}

func testBM2(b *testing.B) {
    for i := 0; i<b.N; i++ {
        json.Marshal(Coll2{&as,&bs,&cs})
    }
}

func main() {
    fmt.Println(testing.Benchmark(testBM1))
    fmt.Println(testing.Benchmark(testBM2))
}

我期望第二种情况运行得更快,因为它使用了指针,所以不需要复制字符串,但实际上它的运行时间约为4250 ns/op,而第一种情况的运行时间接近2800 ns/op。有人能解释一下为什么会这样吗?

编辑:Darshan Computing 提出即使对于嵌套结构体也可能成立。一个简单的测试证实了这一点:

package main
                               
import (
    "fmt"
    "testing"                  
    "encoding/json"            
) 
 
type Coll1 struct {
    A,B,C string
}
 
type Coll1Outer struct {
    A,B,C Coll1
}
 
type Coll2Outer struct {
    A,B,C *Coll2
}
 
type Coll2 struct {
    A,B,C *string
}
 
var as = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
var bs = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
var cs = "ccccccccccccccccccccccccccccccccc"
                                                                                                                                                                                                                                               
func testBM1(b *testing.B) {
    for i := 0; i<b.N; i++ {
        c := Coll1Outer{ Coll1{as,bs,cs}, 
                         Coll1{as,bs,cs},                
                         Coll1{as,bs,cs} }               
        json.Marshal(c)
    }
}
 
func testBM2(b *testing.B) {
    for i := 0; i<b.N; i++ {
        c := Coll2Outer{ &Coll2{&as,&bs,&cs},
                         &Coll2{&as,&bs,&cs},            
                         &Coll2{&as,&bs,&cs} }           
        json.Marshal(c)
    }
}
 
func main() {
    fmt.Println(testing.Benchmark(testBM1))
    fmt.Println(testing.Benchmark(testBM2))
}

对我来说,这显示了非指针结构体大约需要12ms/op,而带有指针的结构体需要13ms/op。虽然差别不大,但有趣的是这个特性仍然成立。

英文:

I have the following test code:

package main

import (
    &quot;fmt&quot;
    &quot;testing&quot;
    &quot;encoding/json&quot;
)

type Coll1 struct {
    A string
    B string
    C string
}

type Coll2 struct {
    A *string
    B *string
    C *string
}

var as = &quot;aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&quot;
var bs = &quot;bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb&quot;
var cs = &quot;ccccccccccccccccccccccccccccccccc&quot;

func testBM1(b *testing.B) {
    for i := 0; i&lt;b.N; i++ {
        json.Marshal(Coll1{as,bs,cs})
    }
}

func testBM2(b *testing.B) {
    for i := 0; i&lt;b.N; i++ {
        json.Marshal(Coll2{&amp;as,&amp;bs,&amp;cs})
    }
}

func main() {
    fmt.Println(testing.Benchmark(testBM1))
    fmt.Println(testing.Benchmark(testBM2))
}

I would expect the second case to run faster since it is using pointers and therefore doesn't have to copy the strings, but in fact it runs at about 4250 ns/op where the first runs near 2800 ns/op. Can anyone shed any light on why this might be?

Edit: Darshan Computing suggested that this may hold true for embedded structs even. A simple test confirms this:

package main
                               
import (
    &quot;fmt&quot;
    &quot;testing&quot;                  
    &quot;encoding/json&quot;            
) 
 
type Coll1 struct {
    A,B,C string
}
 
type Coll1Outer struct {
    A,B,C Coll1
}
 
type Coll2Outer struct {
    A,B,C *Coll2
}
 
type Coll2 struct {
    A,B,C *string
}
 
var as = &quot;aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&quot;
var bs = &quot;bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb&quot;
var cs = &quot;ccccccccccccccccccccccccccccccccc&quot;
                                                                                                                                                                                                                                               
func testBM1(b *testing.B) {
    for i := 0; i&lt;b.N; i++ {
        c := Coll1Outer{ Coll1{as,bs,cs}, 
                         Coll1{as,bs,cs},                
                         Coll1{as,bs,cs} }               
        json.Marshal(c)
    }
}
 
func testBM2(b *testing.B) {
    for i := 0; i&lt;b.N; i++ {
        c := Coll2Outer{ &amp;Coll2{&amp;as,&amp;bs,&amp;cs},
                         &amp;Coll2{&amp;as,&amp;bs,&amp;cs},            
                         &amp;Coll2{&amp;as,&amp;bs,&amp;cs} }           
        json.Marshal(c)
    }
}
 
func main() {
    fmt.Println(testing.Benchmark(testBM1))
    fmt.Println(testing.Benchmark(testBM2))
}

For me this shows the non-pointer struct taking about 12ms/op, while the one with pointers takes 13ms/op. Not a huge difference, but it's interesting that the property still holds.

答案1

得分: 1

我注意到当我将asbscs分别设置为"a""b""c"时,ns/op的百分比差异最大。随着字符串长度的增加,它们趋于接近。它们似乎总是相差约1000 ns/op。

所以我相信发生的一切只是在我的机器上需要1000 ns(在你的机器上需要1450 ns)来反映和跟随指针。一开始传递一个较小的结构似乎不能抵消这种效果,因为一旦跟随指针,Marshal仍然在生成和返回等效的JSON的过程中在内部传递数据。

英文:

I notice the biggest percentage difference in ns/op when I set as, bs, and cs to &quot;a&quot;, &quot;b&quot;, and &quot;c&quot;, respectively. As I increase the length of the strings, they approach each other. They seem to always be about 1000 ns/op different.

So I believe all that's going on is that it takes 1000 ns on my machine (1450 on yours) to reflect and follow the pointers. Passing a smaller struct up front doesn't seem to counteract this effect because once the pointers are followed, Marshal still passes the data around internally in the process of generating and returning the equivalent JSON.

huangapple
  • 本文由 发表于 2013年5月31日 11:19:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/16849237.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定