正文
This collection of practical performance benchmarks of Go packages and algorithms aims to help developers write fast and efficient programs.
The following benchmarks evaluate various functionality with the focus on the usability of benchmark results.
Environment: Go 1.10, Linux, Intel® Core™ i7-4770HQ CPU @ 2.20GHz
String Concatenation
This benchmark evaluates the performance of string concatenation using the
+
operator, the
bytes.Buffer
and the
strings.Builder
when
building a 1,000 character string. The implementations using the
bytes.Buffer
and the
strings.Builder
are the fastest.
package main
import (
"bytes"
"strings"
"testing"
)
var strLen int = 1000
func BenchmarkConcatString(b *testing.B) {
var str string
i := 0
b.ResetTimer()
for n := 0; n < b.N; n++ {
str += "x"
i++
if i >= strLen {
i = 0
str = ""
}
}
}
func BenchmarkConcatBuffer(b *testing.B) {
var buffer bytes.Buffer
i := 0
b.ResetTimer()
for n := 0; n < b.N; n++ {
buffer.WriteString("x")
i++
if i >= strLen {
i = 0
buffer = bytes.Buffer{}
}
}
}
func BenchmarkConcatBuilder(b *testing.B) {
var builder strings.Builder
i := 0
b.ResetTimer()
for n := 0; n < b.N; n++ {
builder.WriteString("x")
i++
if i >= strLen {
i = 0
builder = strings.Builder{}
}
}
}
$ go test -bench=. -benchmem
BenchmarkConcatString-4 10,000,000 159 ns/op 530 B/op 0 allocs/op
BenchmarkConcatBuffer-4 200,000,000 10 ns/op 2 B/op 0 allocs/op
BenchmarkConcatBuilder-4 100,000,000 11 ns/op 2 B/op 0 allocs/op
Numeric Conversions
This benchmark evaluates the performance of parsing strings to
bool
,
int64
and
float64
types using the Go
strconv
package.
package main
import (
"strconv"
"testing"
)
func BenchmarkParseBool(b *testing.B) {
for n := 0; n < b.N; n++ {
_, err := strconv.ParseBool("true")
if err != nil {
panic(err)
}
}
}
func BenchmarkParseInt(b *testing.B) {
for n := 0; n < b.N; n++ {
_, err := strconv.ParseInt("7182818284", 10, 64)
if err != nil {
panic(err)
}
}
}
func BenchmarkParseFloat(b *testing.B) {
for n := 0; n < b.N; n++ {
_, err := strconv.ParseFloat("3.1415926535", 64)
if err != nil {
panic(err)
}
}
}
$ go test -bench=. -benchmem
BenchmarkParseBool-4 300,000,000 4 ns/op 0 B/op 0 allocs/op
BenchmarkParseInt-4 50,000,000 25 ns/op 0 B/op 0 allocs/op
BenchmarkParseFloat-4 50,000,000 40 ns/op 0 B/op 0 allocs/op
Regular Expressions
This benchmark evaluates the performance of regular expression matching using the Go
regexp
package for compiled and uncompiled regular expressions. The example uses a simple email validation regexp. As
expected, the compiled regexp matching is much faster.
package main
import (
"regexp"
"testing"
)
var testRegexp string = `^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]+$`
func BenchmarkMatchString(b *testing.B) {
for n := 0; n < b.N; n++ {
_, err := regexp.MatchString(testRegexp, "[email protected]")
if err != nil {
panic(err)
}
}
}
func BenchmarkMatchStringCompiled(b *testing.B) {
r, err := regexp.Compile(testRegexp)
if err != nil {
panic(err)
}
b.ResetTimer()
for n := 0; n < b.N; n++ {
r.MatchString("[email protected]")
}
}
$ go test -bench=. -benchmem
BenchmarkMatchString-4 100,000 17,380 ns/op 42,752 B/op 70 allocs/op
BenchmarkMatchStringCompiled-4 2,000,000 843 ns/op 0 B/op 0 allocs/op
Sorting
This benchmark evaluates the performance of sorting 1,000, 10,000, 100,000 and 1,000,000
int
elements using built-in sorting algorithm of the Go
sort
package. The time complexity is documented
to be O(n*log(n)), which can be observed in the results.
package main
import (
"math/rand"
"sort"
"testing"
)
func generateSlice(n int) []int {
s := make([]int, 0, n)
for i := 0; i < n; i++ {
s = append(s, rand.Intn(1e9))
}
return s
}
func BenchmarkSort1000(b *testing.B) {
for n := 0; n < b.N; n++ {
b.StopTimer()
s := generateSlice(1000)
b.StartTimer()
sort.Ints(s)
}
}
func BenchmarkSort10000(b *testing.B) {
for n := 0; n < b.N; n++ {
b.StopTimer()
s := generateSlice(10000)
b.StartTimer()
sort.Ints(s)
}
}
func BenchmarkSort100000(b *testing.B) {
for n := 0; n < b.N; n++ {
b.StopTimer()
s := generateSlice(100000)
b.StartTimer()
sort.Ints(s)
}
}
func BenchmarkSort1000000(b *testing.B) {
for n := 0; n < b.N; n++ {
b.StopTimer()
s := generateSlice(1000000)
b.StartTimer()
sort.Ints(s)
}
}
$ go test -bench=. -benchmem
BenchmarkSort1000-4 10,000 121,720 ns/op 32 B/op 1 allocs/op
BenchmarkSort10000-4 1,000 1,477,141 ns/op 32 B/op 1 allocs/op
BenchmarkSort100000-4 100 19,211,037 ns/op 32 B/op 1 allocs/op
BenchmarkSort1000000-4 5 220,539,215 ns/op 32 B/op 1 allocs/op
Random Numbers
This benchmark compares the performance of pseudorandom number generation using the Go
math/rand
and
crypto/rand
packages. The random number generation
with the
math/rand
package is considerably faster than cryptographically secure random number generation with the
crypto/rand
package.
package main
import (
crand "crypto/rand"
"math/big"
"math/rand"
"testing"
)
func BenchmarkMathRand(b *testing.B) {
for n := 0; n < b.N; n++ {
rand.Int63()
}
}
func BenchmarkCryptoRand(b *testing.B) {
for n := 0; n < b.N; n++ {
_, err := crand.Int(crand.Reader, big.NewInt(27))
if err != nil {
panic(err)
}
}
}
$ go test -bench=. -benchmem
BenchmarkMathRand-4 50,000,000 23 ns/op 0 B/op 0 allocs/op
BenchmarkCryptoRand-4 1,000,000 1,336 ns/op 161 B/op 5 allocs/op
Random Strings
This benchmark compares the performance of 16-character, uniformly distributed random string generation based on the Go
math/rand
and
crypto/rand
. The
random string generation with the
math/rand
package is faster than cryptographically secure random string generation with the
crypto/rand
package.
package main
import (
crand "crypto/rand"
"math/rand"
"testing"
)
// 64 letters
const letters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/"
func randomBytes(n int) []byte {
bytes := make([]byte, n)
_, err := rand.Read(bytes)
if err != nil {
panic(err)
}
return bytes
}
func cryptoRandomBytes(n int) []byte {
bytes := make([]byte, n)
_, err := crand.Read(bytes)
if err != nil {
panic(err)
}
return bytes
}
func randomString(bytes []byte) string {
for i, b := range bytes {
bytes[i] = letters[b%64]
}
return string(bytes)
}
func BenchmarkMathRandString(b *testing.B) {
for n := 0; n < b.N; n++ {
randomString(randomBytes(16))
}
}
func BenchmarkCryptoRandString(b *testing.B) {
for n := 0; n < b.N; n++ {
randomString(cryptoRandomBytes(16))
}
}
$ go test -bench=. -benchmem
BenchmarkMathRandString-4 10,000,000 119 ns/op 32 B/op 2 allocs/op
BenchmarkCryptoRandString-4 2,000,000 864 ns/op 32 B/op 2 allocs/op
Slice Appending
This benchmark evaluates the performance of appending a
byte
to a slice with and without slice preallocation.
package main
import (
"testing"
)
var numItems int = 1000000
func BenchmarkSliceAppend(b *testing.B) {
s := make([]byte, 0)
i := 0
b.ResetTimer()
for n := 0; n < b.N; n++ {
s = append(s, 1)
i++
if i == numItems {
b.StopTimer()
i = 0
s = make([]byte, 0)
b.StartTimer()
}
}
}
func BenchmarkSliceAppendPrealloc(b *testing.B) {
s := make([]byte, 0, numItems)
i := 0
b.ResetTimer()
for n := 0; n < b.N; n++ {
s = append(s, 1)
i++
if i == numItems {
b.StopTimer()
i = 0
s = make([]byte, 0, numItems)
b.StartTimer()
}
}
}
$ go test -bench=. -benchmem
BenchmarkSliceAppend-4 1,000,000,000 2 ns/op 5 B/op 0 allocs/op
BenchmarkSliceAppendPrealloc-4 2,000,000,000 1 ns/op 0 B/op 0 allocs/op
Map Access
This benchmark evaluates the access performance of maps with
int
vs
string
keys for 1,000,000 item maps.
package main
import (
"math/rand"
"strconv"
"testing"
)
var NumItems int = 1000000
func BenchmarkMapStringKeys(b *testing.B) {
m := make(map[string]string)
k := make([]string, 0)
for i := 0; i < NumItems; i++ {
key := strconv.Itoa(rand.Intn(NumItems))
m[key] = "value" + strconv.Itoa(i)
k = append(k, key)
}
i := 0
l := len(m)
b.ResetTimer()
for n := 0; n < b.N; n++ {
if _, ok := m[k[i]]; ok {
}
i++
if i >= l {
i = 0
}
}
}
func BenchmarkMapIntKeys(b *testing.B) {
m := make(map[int]string)
k := make([]int, 0)
for i := 0; i < NumItems; i++ {
key := rand.Intn(NumItems)
m[key] = "value" + strconv.Itoa(i)
k = append(k, key)
}
i := 0
l := len(m)
b.ResetTimer()
for n := 0; n < b.N; n++ {
if _, ok := m[k[i]]; ok {
}
i++
if i >= l {
i = 0
}
}
}
$ go test -bench=. -benchmem
BenchmarkMapStringKeys-4 20,000,000 107 ns/op 0 B/op 0 allocs/op
BenchmarkMapIntKeys-4 20,000,000 65 ns/op 0 B/op 0 allocs/op
Object Pool
This benchmark evaluates the performance of object creation vs object reuse using
sync.Pool
.
package main
import (
"sync"
"testing"
)
type Book struct {
Title string
Author string
Pages int
Chapters []string
}
var pool = sync.Pool{
New: func() interface{} {
return &Book{}
},
}
func BenchmarkNoPool(b *testing.B) {
var book *Book
for n := 0; n < b.N; n++ {
book = &Book{
Title: "The Art of Computer Programming, Vol. 1",
Author: "Donald E. Knuth",
Pages: 672,
}
}
_ = book
}
func BenchmarkPool(b *testing.B) {
for n := 0; n < b.N; n++ {
book := pool.Get().(*Book)
book.Title = "The Art of Computer Programming, Vol. 1"
book.Author = "Donald E. Knuth"
book.Pages = 672
pool.Put(book)
}
}
$ go test -bench=. -benchmem
BenchmarkNoPool-4 30,000,000 45 ns/op 64 B/op 1 allocs/op
BenchmarkPool-4 100,000,000 22 ns/op 0 B/op 0 allocs/op
Hash Functions
This benchmark compares the performance of multiple hash functions, including MD5, SHA1, SHA256, SHA512, SHA3-256, SHA3-512, BLAKE2d-256 and BLAKE2d-256 from internal and external Go
crypto
subpackages on
random one kilobyte data.