Golang performance benchmark and deep into the middle code: a string operation example
Context
Recently, I’m looking for efficient string manipulation in Golang, during the process of that, I used Golang benchmark to test the execution time and checked the Golang middle code to know the underlying mechanism between different solutions.
The problem
Given a long string, replace the given character with the other given character in it and return the new string.
For example, the string is “aaaaaa”, replace “a” with “b”, after replacement, the return string will be “bbbbbb”.
All the following code can be found at Github Gist.
Solution 1: string concatenation
1 | func replaceChar1(str string, ch, replaceCh byte) string { |
Solution 2: Byte array
1 | func replaceChar2(str string, ch, replaceCh byte) string { |
Solution 3: String builder
1 | func replaceChar3(str string, ch, replaceCh byte) string { |
Performance Comparison
With 3 solutions right now, I want to know which one is more efficient, Golang benchmark is a good way of comparing the operation efficiency of these.
- Add a test file with
_test
suffix - Add functions with
Benchmark
as prefix like
1 | func BenchmarkStringReplace1(b *testing.B) { |
- Run the benchmark
The benchmark function must run the target code b.N times. During benchmark execution, b.N is adjusted until the benchmark function lasts long enough to be timed reliably.
1 | $ go test -bench=. string_op_benchmark.go string_op_benchmark_test.go |
The output means that the loop BenchmarkStringReplace1-10
ran 3 times at a speed of 389948722 ns per loop.
The BenchmarkStringReplace2 behaved the best and BenchmarkStringReplace1-10 (String concatenation) behaved the worst.
One step further
What if I’m still curious about the behind scene of the code? I want to know what replaceChar2
did and the middle code it executed. The following command will generate the middle code of the replaceChar2
function.
1 | $ GOSSAFUNC=replaceChar2 go build string_op_benchmark.go |
Open the ssa.html, we can see the original code of replaceChar2
and its middle code, like
bytes := []byte(str)
is mapped to runtime.stringtoslicebyte
, return string(bytes)
is mapped to runtime.slicebytetostring
If we want to make the string operation faster, we might reduce the memory copy or find the other operations we can eliminate.