在Golang中测试功能

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?

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
$

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