|
| 1 | +# Reading benchmark results |
| 2 | + |
| 3 | +Go has benchmarking capabilities as part of it's standard toolchain. |
| 4 | + |
| 5 | +If you want to do a benchmark of your current Go programming project: |
| 6 | + |
| 7 | +```bash |
| 8 | +$ go test -bench . -count 3 |
| 9 | +goos: darwin |
| 10 | +goarch: amd64 |
| 11 | +pkg: twofer |
| 12 | +BenchmarkShareWith-8 14216751 81.7 ns/op |
| 13 | +BenchmarkShareWith-8 13949208 81.1 ns/op |
| 14 | +BenchmarkShareWith-8 14090535 82.4 ns/op |
| 15 | +PASS |
| 16 | +ok twofer 3.769s |
| 17 | +``` |
| 18 | + |
| 19 | +_NB: I have condensed the output a bit for readability, by collapsing excessive whitespace_. |
| 20 | + |
| 21 | +It does require that the test suite has a benchmarking function. |
| 22 | + |
| 23 | +This runs our test suite and benchmarks it, with 3 iterations, specified by the `-count` parameter. |
| 24 | + |
| 25 | +As you can read from the example output. |
| 26 | + |
| 27 | +- 3 iterations are run |
| 28 | +- First column is the test run, which points to a function in the `two_fer_test.go` |
| 29 | +- Second column is the amount of times the test was run |
| 30 | +- Third column is the measured time and yes `ns` is [nanoseconds](https://en.wikipedia.org/wiki/Nanosecond) |
| 31 | + |
| 32 | +We both specify a number of iterations, but the test suite does an internal count, I have quoted the explanation from [the official documentation][pkgtesting]: |
| 33 | + |
| 34 | +> The benchmark function must run the target code b.N times. During benchmark execution, b.N is |
| 35 | +> adjusted until the benchmark function lasts long enough to be timed reliably. |
| 36 | +
|
| 37 | +Please see the official documentation for more information. |
| 38 | + |
| 39 | +Now if you want even more detail, you can also get numbers on memory allocation using the `-benchmem` flag. There are plenty of flags, [check the official documentation][testingflags] for a listing. |
| 40 | + |
| 41 | +```bash |
| 42 | +$ go test -bench . -benchmem -count 3 |
| 43 | +goos: darwin |
| 44 | +goarch: amd64 |
| 45 | +pkg: twofer |
| 46 | +BenchmarkShareWith-8 14013910 82.4 ns/op 0 B/op 0 allocs/op |
| 47 | +BenchmarkShareWith-8 14102596 81.7 ns/op 0 B/op 0 allocs/op |
| 48 | +BenchmarkShareWith-8 14164332 81.1 ns/op 0 B/op 0 allocs/op |
| 49 | +PASS |
| 50 | +ok twofer 3.873s |
| 51 | +``` |
| 52 | + |
| 53 | +_NB: I have condensed the output a bit for readability, by collapsing excessive whitespace_. |
| 54 | + |
| 55 | +This runs our test suite again and benchmarks it, with 3 iterations, but outputs more information. |
| 56 | + |
| 57 | +_Since I was unable to understand the output, I had to ask around and I received the following explanation_. Thanks to **mzi** from the Gopher slack for the explanation. |
| 58 | + |
| 59 | +> B/op is how many bytes were allocated per iteration, and allocs/op is how many allocations were |
| 60 | +> made. (per iteration) |
| 61 | +
|
| 62 | +The first 3 columns are the ones from the basic benchmark. So with the `--benchmem` flag, two more columns are added: |
| 63 | + |
| 64 | +- Fourth column is the number of _extra_ bytes allocated per iteration |
| 65 | +- Fifth column is the number of _extra_ allocations made per iteration |
| 66 | + |
| 67 | +And finally thanks to **superstas** at Exercism.io for pushing me into benchmarking my first real Go solution. |
| 68 | + |
| 69 | +To describe more of what I learned, lets examine my solution from [Exercism.io][Exercism.io]. |
| 70 | + |
| 71 | +```go |
| 72 | +package twofer |
| 73 | + |
| 74 | +func ShareWith(name string) string { |
| 75 | + if name == "" { |
| 76 | + name = "you" |
| 77 | + } |
| 78 | + |
| 79 | + return "One for " + name + ", one for me." |
| 80 | +} |
| 81 | +``` |
| 82 | + |
| 83 | +_NB: I have condensed the output a bit for readability, by removing inline documentation_. |
| 84 | + |
| 85 | +This was the optimized solution, as pointed out by **superstas** could be made simpler and less expernsive resource wise. |
| 86 | + |
| 87 | +The solution proposal he was giving feedback on was this one: |
| 88 | + |
| 89 | +```go |
| 90 | +package twofer |
| 91 | + |
| 92 | +import "fmt" |
| 93 | + |
| 94 | +func ShareWith(name string) string { |
| 95 | + if name == "" { |
| 96 | + name = "you" |
| 97 | + } |
| 98 | + |
| 99 | + return fmt.Sprintf("One for %s, one for me.", name) |
| 100 | +} |
| 101 | +``` |
| 102 | + |
| 103 | +_NB: I have condensed the output a bit for readability, by removing inline documentation_. |
| 104 | + |
| 105 | +If we benchmark this you can compare the numbers: |
| 106 | + |
| 107 | +```bash |
| 108 | +$ go test -bench . -benchmem -count 3 |
| 109 | +goos: darwin |
| 110 | +goarch: amd64 |
| 111 | +pkg: twofer |
| 112 | +BenchmarkShareWith-8 2691262 443 ns/op 144 B/op 6 allocs/op |
| 113 | +BenchmarkShareWith-8 2716256 443 ns/op 144 B/op 6 allocs/op |
| 114 | +BenchmarkShareWith-8 2702143 447 ns/op 144 B/op 6 allocs/op |
| 115 | +PASS |
| 116 | +ok twofer 5.207s |
| 117 | +``` |
| 118 | + |
| 119 | +_NB: I have condensed the output a bit for readability, by collapsing excessive whitespace_. |
| 120 | + |
| 121 | +As pointed out by **superstas** use of Go's `fmt` package comes at a certain price and in this case a basic concatenation would suffice as demonstrated in the example with the optimized solution. |
| 122 | + |
| 123 | +With Go benchmarking at the tip of your fingers, you can gain insights on your code, read the documentation and start experimenting and perhaps even optimizing. |
| 124 | + |
| 125 | +## References |
| 126 | + |
| 127 | +- [Exercism.io](https://exercism.io/) |
| 128 | +- [My exercism.io "two-fer" solution](https://exercism.io/tracks/go/exercises/two-fer/solutions/670e02e265634d3ab83821b0649f83c7) |
| 129 | +- [Gophers Slack](https://invite.slack.golangbridge.org/) |
| 130 | +- [Go package: "testing"][pkgtesting] |
| 131 | +- [Go command: Testing flags][testingflags] |
| 132 | + |
| 133 | +[pkgtesting]: https://golang.org/pkg/testing/ |
| 134 | +[testingflags]: https://golang.org/cmd/go/#hdr-Testing_flags |
0 commit comments