专栏名称: 狗厂
目录
相关文章推荐
51好读  ›  专栏  ›  狗厂

Practical Go Benchmarks

狗厂  · 掘金  ·  · 2018-03-08 02:37

正文

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.







请到「今天看啥」查看全文