英文:
Testing a function in golang
问题
所以我决定为我的 golang
函数编写测试。函数本身如下所示:
func Insert(slice []int, element int, index int) []int {
n := len(slice)
slice = slice[:(n + 1)]
for i := index; i < n; i++ {
slice[i+1] = slice[i]
}
slice[index] = element
return slice
}
它接受一个切片,将其长度增加1,并在给定的索引处插入一个元素。现在,在调用它之前,我必须做两件事:
size := somevalue
array := make([]int, size, size+1)
然后,例如 array = Insert(array, 1, len(array)/2)
。现在我编写了 insert_test.go
:
package sdizo
import "testing"
func benchmarkInsert(size int, b *testing.B) {
b.ResetTimer()
for n := 0; n < b.N; n++ {
array := make([]int, size, size+1)
array = Insert(array, 1, len(array)/2)
}
}
func BenchmarkInsert10k(b *testing.B) { benchmarkInsert(10000, b) }
func BenchmarkInsert20k(b *testing.B) { benchmarkInsert(20000, b) }
func BenchmarkInsert30k(b *testing.B) { benchmarkInsert(30000, b) }
func BenchmarkInsert40k(b *testing.B) { benchmarkInsert(40000, b) }
func BenchmarkInsert50k(b *testing.B) { benchmarkInsert(50000, b) }
问题是,在测试循环中有两个操作。我不能将 make()
移到循环上面,因为每次尝试插入时都必须创建一个新的数组(不想干扰容量)。它可以工作,它给我输出,但我想知道这个 make()
是否会影响时间测量,并且我想问你是否可以以某种方式仅测量 Insert()
的时间。
附注:是否有一种方便的方法将基准测试的结果写入文件?
英文:
So I've decided to write test for my function in golang
. The function itself looks like this:
func Insert(slice []int, element int, index int) []int {
n := len(slice)
slice = slice[:(n + 1)]
for i := index; i < n; i++ {
slice[i+1] = slice[i]
}
slice[index] = element
return slice
}
So it takes a slice, extends its length by 1 and inserts an element at given index. Now before I call it I have to do 2 things:
size := somevalue
array := make([]int, size, size+1)
and then f.e array = Insert(array, 1, len(array)/2
. Now I wrote insert_test.go
:
package sdizo
import "testing"
func benchmarkInsert(size int, b *testing.B) {
b.ResetTimer()
for n := 0; n < b.N; n++ {
array := make([]int, size, size+1)
array = Insert(array, 1, len(array)/2)
}
}
func BenchmarkInsert10k(b *testing.B) { benchmarkInsert(10000, b) }
func BenchmarkInsert20k(b *testing.B) { benchmarkInsert(20000, b) }
func BenchmarkInsert30k(b *testing.B) { benchmarkInsert(30000, b) }
func BenchmarkInsert40k(b *testing.B) { benchmarkInsert(40000, b) }
func BenchmarkInsert50k(b *testing.B) { benchmarkInsert(50000, b) }
The thing is, there are 2 operations in testing loop. I cannot move the make()
above the loop, cuz it has to make a new array everytime it tries to insert something to it (don't want to mess with capacity). It works, it gives me output, but I am curious if this make()
doesn't mess up with time measurement, and I would like to ask you if I can somehow measure the time only for Insert()
P.S Is there a convenient way to write down the results of benchmark test to a file?
答案1
得分: 1
编辑:你所寻求的真正答案。(终于)
当你进行基准测试时,确保只测试你想要测试的内容。你的使用情况不会每次都进行创建和插入,所以只需创建一次,然后在循环中测试插入操作。重点是只测试瓶颈部分。
在这里,你只需要测试Insert
操作。你可以通过每次调整数组大小来实现,这个操作的成本基本上可以忽略不计(至少与make
或Insert
相比)。以下是示例代码:
func benchmarkInsert(size int, b *testing.B) {
array := make([]int, size, size+1)
b.ResetTimer()
for n := 0; n < b.N; n++ {
array = Insert(array, 1, len(array)/2)
array = array[0:size]
}
}
要解释为什么这是最小化的,你必须意识到,在深层次上,切片本质上是一个类似于以下结构的结构体:
type Slice struct {
contents []interface{}
length int
capacity int
}
使用array = array[0:size]
来调整大小的操作类似于将结构体的length
设置为size
。详细信息请参阅:https://blog.golang.org/go-slices-usage-and-internals
至于将结果写入文件,这取决于你的操作系统。在任何Unix系统上,只需在基准测试命令的末尾添加> file.txt
即可将结果导入到file.txt
中。关于Windows系统我不太确定。
编辑:添加更多的测试结果显示这是线性扩展的。
BenchmarkInsert10k-8 200000 7889 ns/op
BenchmarkInsert20k-8 100000 16131 ns/op
BenchmarkInsert30k-8 100000 24184 ns/op
BenchmarkInsert40k-8 50000 31767 ns/op
BenchmarkInsert50k-8 50000 39721 ns/op
BenchmarkInsert100k-8 20000 79711 ns/op
BenchmarkInsert200k-8 10000 159411 ns/op
BenchmarkInsert300k-8 5000 237188 ns/op
BenchmarkInsert400k-8 5000 316270 ns/op
BenchmarkInsert500k-8 3000 399146 ns/op
BenchmarkInsert1000k-8 2000 793845 ns/op
使用以下代码进行测试:https://play.golang.org/p/6fWHwpzUJE
英文:
EDIT: The real answer you seek. (Finally)
When you benchmark, actually benchmark what you want to benchmark. Your use case isn't going to be a make, then insert every time, so just make once, then test insert in the loop. The point is to test ONLY the chokepoint.
What you want to do here is just test Insert
. You can do this by simply resizing the array each time, the cost of which is basically nothing (at least in comparison to make
or Insert
).
func benchmarkInsert(size int, b *testing.B) {
array := make([]int, size, size+1)
b.ResetTimer()
for n := 0; n < b.N; n++ {
array = Insert(array, 1, len(array)/2)
array = array[0:size]
}
}
To explain why it is minimal, you have to realize that deep down, a slice is essentially a struct that looks something like this:
type Slice struct {
contents []interface{}
length int
capacity int
}
Performing the operation to resize it using array = array[0:size]
is analogous to setting the struct's length=size
. See https://blog.golang.org/go-slices-usage-and-internals
As far as writing the results to file, that depends on your operating system. On any Unix system, simply running the benchmark command with > file.txt
at the end will pipe the results to file.txt
. Not sure about Windows.
EDIT: Adding more tests shows this scales linearly.
BenchmarkInsert10k-8 200000 7889 ns/op
BenchmarkInsert20k-8 100000 16131 ns/op
BenchmarkInsert30k-8 100000 24184 ns/op
BenchmarkInsert40k-8 50000 31767 ns/op
BenchmarkInsert50k-8 50000 39721 ns/op
BenchmarkInsert100k-8 20000 79711 ns/op
BenchmarkInsert200k-8 10000 159411 ns/op
BenchmarkInsert300k-8 5000 237188 ns/op
BenchmarkInsert400k-8 5000 316270 ns/op
BenchmarkInsert500k-8 3000 399146 ns/op
BenchmarkInsert1000k-8 2000 793845 ns/op
Using this code: https://play.golang.org/p/6fWHwpzUJE
答案2
得分: 1
例如(从Go 1.7开始),使用您的Insert
算法:
package sdizo
import (
"strconv"
"testing"
)
func Insert(slice []int, element int, index int) []int {
n := len(slice)
slice = slice[:(n + 1)]
for i := index; i < n; i++ {
slice[i+1] = slice[i]
}
slice[index] = element
return slice
}
func BenchmarkInsert(b *testing.B) {
for size := 10000; size <= 50000; size += 10000 {
b.Run(strconv.Itoa(size/1000)+"k",
func(b *testing.B) {
a := make([]int, size, size+1)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
a = a[:size]
a = Insert(a, 1, len(a)/2)
}
b.StopTimer()
},
)
}
}
输出:
$ go test -bench=.
goos: linux
goarch: amd64
pkg: sdizo
BenchmarkInsert/10k-4 50000 32502 ns/op 0 B/op 0 allocs/op
BenchmarkInsert/20k-4 20000 64364 ns/op 0 B/op 0 allocs/op
BenchmarkInsert/30k-4 20000 97310 ns/op 0 B/op 0 allocs/op
BenchmarkInsert/40k-4 10000 129196 ns/op 0 B/op 0 allocs/op
BenchmarkInsert/50k-4 10000 161427 ns/op 0 B/op 0 allocs/op
PASS
ok so/sdizo 9.778s
$
现在,我们可以对您的`Insert`算法进行基准测试,然后利用这些知识来改进算法。例如:
```go
package sdizo
import (
"strconv"
"testing"
)
func Insert(slice []int, element int, index int) []int {
slice = slice[:len(slice)+1]
copy(slice[index+1:], slice[index:])
slice[index] = element
return slice
}
func BenchmarkInsert(b *testing.B) {
for size := 10000; size <= 50000; size += 10000 {
b.Run(strconv.Itoa(size/1000)+"k",
func(b *testing.B) {
a := make([]int, size, size+1)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
a = a[:size]
a = Insert(a, 1, len(a)/2)
}
b.StopTimer()
},
)
}
}
输出:
$ go test -bench=.
goos: linux
goarch: amd64
pkg: sdizo
BenchmarkInsert/10k-4 200000 7664 ns/op 0 B/op 0 allocs/op
BenchmarkInsert/20k-4 100000 15208 ns/op 0 B/op 0 allocs/op
BenchmarkInsert/30k-4 100000 22959 ns/op 0 B/op 0 allocs/op
BenchmarkInsert/40k-4 50000 35181 ns/op 0 B/op 0 allocs/op
BenchmarkInsert/50k-4 50000 39658 ns/op 0 B/op 0 allocs/op
PASS
ok so/sdizo 10.331s
$
英文:
For example (from Go 1.7 forward), using your Insert
algorithm,
package sdizo
import (
"strconv"
"testing"
)
func Insert(slice []int, element int, index int) []int {
n := len(slice)
slice = slice[:(n + 1)]
for i := index; i < n; i++ {
slice[i+1] = slice[i]
}
slice[index] = element
return slice
}
func BenchmarkInsert(b *testing.B) {
for size := 10000; size <= 50000; size += 10000 {
b.Run(strconv.Itoa(size/1000)+"k",
func(b *testing.B) {
a := make([]int, size, size+1)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
a = a[:size]
a = Insert(a, 1, len(a)/2)
}
b.StopTimer()
},
)
}
}
Output:
$ go test -bench=.
goos: linux
goarch: amd64
pkg: sdizo
BenchmarkInsert/10k-4 50000 32502 ns/op 0 B/op 0 allocs/op
BenchmarkInsert/20k-4 20000 64364 ns/op 0 B/op 0 allocs/op
BenchmarkInsert/30k-4 20000 97310 ns/op 0 B/op 0 allocs/op
BenchmarkInsert/40k-4 10000 129196 ns/op 0 B/op 0 allocs/op
BenchmarkInsert/50k-4 10000 161427 ns/op 0 B/op 0 allocs/op
PASS
ok so/sdizo 9.778s
$
Now that we can benchmark your Insert
algorithm, we can use that knowledge to improve the algorithm. For example,
package sdizo
import (
"strconv"
"testing"
)
func Insert(slice []int, element int, index int) []int {
slice = slice[:len(slice)+1]
copy(slice[index+1:], slice[index:])
slice[index] = element
return slice
}
func BenchmarkInsert(b *testing.B) {
for size := 10000; size <= 50000; size += 10000 {
b.Run(strconv.Itoa(size/1000)+"k",
func(b *testing.B) {
a := make([]int, size, size+1)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
a = a[:size]
a = Insert(a, 1, len(a)/2)
}
b.StopTimer()
},
)
}
}
Output:
$ go test -bench=.
goos: linux
goarch: amd64
pkg: sdizo
BenchmarkInsert/10k-4 200000 7664 ns/op 0 B/op 0 allocs/op
BenchmarkInsert/20k-4 100000 15208 ns/op 0 B/op 0 allocs/op
BenchmarkInsert/30k-4 100000 22959 ns/op 0 B/op 0 allocs/op
BenchmarkInsert/40k-4 50000 35181 ns/op 0 B/op 0 allocs/op
BenchmarkInsert/50k-4 50000 39658 ns/op 0 B/op 0 allocs/op
PASS
ok so/sdizo 10.331s
$
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论