Skip to content

Instantly share code, notes, and snippets.

@montanaflynn
Last active February 26, 2020 09:23
Show Gist options
  • Save montanaflynn/58df9a48e40baca8dcdc to your computer and use it in GitHub Desktop.
Save montanaflynn/58df9a48e40baca8dcdc to your computer and use it in GitHub Desktop.
String concatenation benchmarks in Golang inspired by https://www.reddit.com/r/golang/comments/3nrccd/return_fmtsprintf_vs_string/
package main
import (
"fmt"
"strconv"
"testing"
)
var (
host string
port int
)
func init() {
host = "localhost"
port = 4444
}
func HostStringSprintf() string {
return fmt.Sprintf("%s:%d", host, port)
}
func HostStringStrconv() string {
return host + ":" + strconv.Itoa(port)
}
func checkResult(t *testing.T, hostStr string) {
expected := "localhost:4444"
if hostStr != expected {
t.Fatal(hostStr, "!=", expected)
}
}
func TestSprintf(t *testing.T) {
hostStr := HostStringSprintf()
checkResult(t, hostStr)
}
func BenchmarkSprintf(b *testing.B) {
for n := 0; n < b.N; n++ {
HostStringSprintf()
}
}
func BenchmarkStrconv(b *testing.B) {
for n := 0; n < b.N; n++ {
HostStringStrconv()
}
}
@montanaflynn
Copy link
Author

Results on single core server running Ubuntu 15.04 and Golang 1.4.1:

$ go test -bench=.
PASS
BenchmarkSprintf      500000          2952 ns/op
BenchmarkStrconv     2000000           991 ns/op
ok      github.com/montanaflynn/string_concat   4.418s

@simon-xia
Copy link

with my go1.6.2 darwin/amd64

BenchmarkSprintf-4   3000000           424 ns/op
BenchmarkStrconv-4  10000000           163 ns/op

@bengadbois
Copy link

go1.7.4 darwin/amd64

BenchmarkSprintf-4   	 5000000	       257 ns/op
BenchmarkStrconv-4   	20000000	       101 ns/op

@beyondkmp
Copy link

go version go1.8.3 linux/amd64

BenchmarkSprintf-2   	 5000000	       248 ns/op
BenchmarkStrconv-2   	20000000	       106 ns/op
PASS
ok  	command-line-arguments	3.751s

@lewapkon
Copy link

go1.9 linux/amd64

BenchmarkSprintf-8   	10000000	       172 ns/op
BenchmarkStrconv-8   	20000000	        75.3 ns/op

@tsafin
Copy link

tsafin commented Mar 1, 2018

go1.10 windows/amd64

goos: windows
goarch: amd64
pkg: bench
BenchmarkSprintf-8      10000000               169 ns/op
BenchmarkStrconv-8      20000000                72.3 ns/op
PASS
ok      bench   3.459s

@sarathsp06
Copy link

go version go1.11 linux/amd64

goos: linux
goarch: amd64
BenchmarkSprintf-4   	10000000	       179 ns/op
BenchmarkStrconv-4   	20000000	        75.0 ns/op

@tomocrafter
Copy link

go version go1.12.1 windows/amd64

goos: windows
goarch: amd64
BenchmarkSprintf-16     10000000               193 ns/op
BenchmarkStrconv-16     20000000                60.4 ns/op

@bradyellison
Copy link

go version go1.12.3 darwin/amd64

goos: darwin
goarch: amd64
BenchmarkSprintf-12    	10000000	       151 ns/op
BenchmarkStrconv-12    	30000000	        45.4 ns/op

@bradyellison
Copy link

bradyellison commented Apr 11, 2019

Added two more strategies:

package main

import (
	"fmt"
	"strconv"
	"testing"
	"unsafe"
)

var (
	host string
	port int
)

func init() {
	host = "localhost"
	port = 4444
}

func HostStringSprintf() string {
	return fmt.Sprintf("%s:%d", host, port)
}

func HostStringStrconv() string {
	return host + ":" + strconv.Itoa(port)
}

func HostStringStrconvBuffer() string {
	buf := append([]byte(host), ':')
	buf = strconv.AppendInt(buf, int64(port), 10)
	return string(buf)
}

// This makes some really terrible assumptions about the way strings are handled in go,
// it is probably a better idea to use strings.Builder when actually doing unsafe things
// with strings a []byte.
func HostStringStrconvBufferUnsafe() string {
	buf := append([]byte(host), ':')
	buf = strconv.AppendInt(buf, int64(port), 10)
	return *(*string)(unsafe.Pointer(&buf))
}

func checkResult(t *testing.T, hostStr string) {
	expected := "localhost:4444"
	if hostStr != expected {
		t.Fatal(hostStr, "!=", expected)
	}
}

func TestSprintf(t *testing.T) {
	hostStr := HostStringSprintf()
	checkResult(t, hostStr)
}

func TestHostStringStrConv(t *testing.T) {
	hostStr := HostStringStrconv()
	checkResult(t, hostStr)
}

func TestHostStringStrconvBuffer(t *testing.T) {
	hostStr := HostStringStrconvBuffer()
	checkResult(t, hostStr)
}

func TestHostStringStrconvBufferUnsafe(t *testing.T) {
	hostStr := HostStringStrconvBufferUnsafe()
	checkResult(t, hostStr)
}

func BenchmarkSprintf(b *testing.B) {
	for n := 0; n < b.N; n++ {
		HostStringSprintf()
	}
}

func BenchmarkStrconv(b *testing.B) {
	for n := 0; n < b.N; n++ {
		HostStringStrconv()
	}
}

func BenchmarkStrconvBuffer(b *testing.B) {
	for n := 0; n < b.N; n++ {
		HostStringStrconvBuffer()
	}
}

func BenchmarkStrconvBufferUnsafe(b *testing.B) {
	for n := 0; n < b.N; n++ {
		HostStringStrconvBufferUnsafe()
	}
}

go version go1.12.3 darwin/amd64

goos: darwin
goarch: amd64
BenchmarkSprintf-12                	10000000	       150 ns/op
BenchmarkStrconv-12                	30000000	        44.7 ns/op
BenchmarkStrconvBuffer-12          	100000000	        22.0 ns/op
BenchmarkStrconvBufferUnsafe-12    	100000000	        18.0 ns/op

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment