英文:
Pointer receiver not faster than value receiver when benchmarking
问题
这是我正在测试的代码,我原本期望在进行基准测试时,基于指针的addDataPointer函数的性能会比基于值的addData函数更快。为什么两者之间的性能没有明显的变化呢?
package main
import "fmt"
type BigStruct struct {
name string
data []byte
}
func addData(s BigStruct) BigStruct {
s.data = append([]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05}, s.data...)
return BigStruct{name: s.name, data: s.data}
}
func (s *BigStruct) addDataPointer() {
s.data = append([]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05}, s.data...)
}
func main() {
a := BigStruct{name: "greg", data: []byte("abc")}
b := &BigStruct{name: "greg", data: []byte("abc")}
fmt.Println(addData(a))
b.addDataPointer()
fmt.Println(*b)
}
func BenchmarkBigLenPointer(b *testing.B) {
for i := 0; i < b.N; i++ {
big := &BigStruct{name: "greg", data: []byte(strings.Repeat("test1234", 1024))}
big.addDataPointer()
}
}
func BenchmarkBigLen(b *testing.B) {
for i := 0; i < b.N; i++ {
big := BigStruct{name: "greg", data: []byte(strings.Repeat("test1234", 1024))}
addData(big)
}
}
这段代码定义了一个BigStruct
结构体,其中包含一个name
字符串和一个data
字节切片。addData
函数接收一个BigStruct
值作为参数,并在data
切片的开头添加一些字节。addDataPointer
方法接收一个BigStruct
指针作为接收者,并在data
切片的开头添加一些字节。
在main
函数中,我们创建了一个BigStruct
值a
和一个BigStruct
指针b
,并分别调用了addData
函数和addDataPointer
方法。然后打印了它们的结果。
BenchmarkBigLenPointer
和BenchmarkBigLen
是基准测试函数,分别对基于指针和基于值的函数进行性能测试。它们使用testing.B
类型的参数来控制测试的次数。
你之所以没有看到两者之间性能的显著变化,可能是因为测试的数据量不够大,或者在你的环境中,基于指针和基于值的函数执行时间相差不大。你可以尝试增加测试数据的大小或者在不同的环境中运行测试来观察性能差异。
英文:
Here is the code I'm testing, I was expecting to see that when bench marking, the pointer based addDataPointer would perform faster than the addData value based function. Why is there no significant changes in performance between the two?
package main
import "fmt"
type BigStruct struct {
name string
data []byte
}
func addData(s BigStruct) BigStruct {
s.data = append([]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05}, s.data...)
return BigStruct{name: s.name, data: s.data}
}
func (s *BigStruct) addDataPointer() {
s.data = append([]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05}, s.data...)
}
func main() {
a := BigStruct{name: "greg", data: []byte("abc")}
b := &BigStruct{name: "greg", data: []byte("abc")}
fmt.Println(addData(a))
b.addDataPointer()
fmt.Println(*b)
}
func BenchmarkBigLenPointer(b *testing.B) {
for i := 0; i < b.N; i++ {
big := &BigStruct{name: "greg", data: []byte(strings.Repeat("test1234", 1024))}
big.addDataPointer()
}
}
func BenchmarkBigLen(b *testing.B) {
for i := 0; i < b.N; i++ {
big := BigStruct{name: "greg", data: []byte(strings.Repeat("test1234", 1024))}
addData(big)
}
答案1
得分: 1
您的基准函数测量的是for
循环内部的内容,例如:
big := &BigStruct{name: "greg", data: []byte(strings.Repeat("test1234", 1024))}
big.addDataPointer()
和:
big := BigStruct{name: "greg", data: []byte(strings.Repeat("test1234", 1024))}
addData(big)
因此,您还对strings.Repeat()
进行了基准测试,它会生成一个很长的string
值,并将其转换为[]byte
,这会复制这个长字符串。
这些操作的执行时间比addDataPointer()
方法和addData()
函数的执行时间要长得多。
将转换和strings.Repeat()
调用移到for
循环之外,类似这样:
func BenchmarkBigLenPointer(b *testing.B) {
s := []byte(strings.Repeat("test1234", 1024))
for i := 0; i < b.N; i++ {
big := &BigStruct{name: "greg", data: s}
big.addDataPointer()
}
}
func BenchmarkBigLen(b *testing.B) {
s := []byte(strings.Repeat("test1234", 1024))
for i := 0; i < b.N; i++ {
big := BigStruct{name: "greg", data: s}
addData(big)
}
}
现在,这样更准确地测量了您的方法和函数的时间。但是,即使在对此进行基准测试时,您将得到显示addData()
和addDataPointer()
执行时间非常接近的基准结果。
这是因为在函数的情况下,您传递了一个包含字节切片的结构值。在Go中,切片是小型描述符(类似结构体的头部),它们不包含切片的元素,而只包含指向支持数组的指针。因此,切片值的大小是相同的,无论其长度(元素数量)如何。要查看切片头部的内容,请查看reflect.SliceHeader
类型。
因此,函数的开销很小,这可能会对其执行时间产生一点影响,但另一方面,指针方法需要解引用指针,这会增加一点执行时间。最终,它们非常接近。在这种情况下,没有太大的区别。
英文:
Your benchmark functions measure whatever is inside the for
loops, e.g.:
big := &BigStruct{name: "greg", data: []byte(strings.Repeat("test1234", 1024))}
big.addDataPointer()
And:
big := BigStruct{name: "greg", data: []byte(strings.Repeat("test1234", 1024))}
addData(big)
So you also benchmark strings.Repeat()
, which gives you a long string
value, and you also convert it to []byte
, which makes a copy of this lengthy string
.
The execution times of these are much bigger than the execution times of your addDataPointer()
method and addData()
function.
Move the conversion and strings.Repeat()
call outside of the for
loops, something like this:
func BenchmarkBigLenPointer(b *testing.B) {
s := []byte(strings.Repeat("test1234", 1024))
for i := 0; i < b.N; i++ {
big := &BigStruct{name: "greg", data: s}
big.addDataPointer()
}
}
func BenchmarkBigLen(b *testing.B) {
s := []byte(strings.Repeat("test1234", 1024))
for i := 0; i < b.N; i++ {
big := BigStruct{name: "greg", data: s}
addData(big)
}
}
Now this is more accurate to measure the time of your method and function. But even when you benchmark this, you will get benchmark results that show that execution times of both addData()
and addDataPointer()
are very close.
The explanation for this is that in case of the function you pass a struct value containing a byte slice. Slices in Go are small descriptors (struct like headers), which do not contain the elements of the slices but only a pointer to a backing array. So the size of a slice value is the same, regardless of its length (number of elements). To see what's inside a slice header, check out the reflect.SliceHeader
type.
So the overhead of the function is little, which might give a little to its execution time, but the pointer method on the other hand needs to dereference the pointer, which adds a little to its execution time. In the end, they are very close. In this case there is not much difference.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论