Zero allocated byte to string conversion
Go has a built-in function for converting a slice of bytes to a string, but it involves allocating a new string. If you want to avoid this allocation, you can use a trick that involves the unsafe
package.
I recently learned about this trick from a tweet in prometheus repository and followed the discussion on Github. It turns out that this trick was originally used in the strings
package, and it works by using type coercion to convert the memory address of the slice to a string pointer, and then dereferencing that pointer to get the string value. Here is the code:
func ByteSlice2String(b []byte) string {
if len(b) == 0 {
return ""
}
return *((*string)(unsafe.Pointer(&b)))
}
Here's how it works:
unsafe.Pointer(&bs)
takes the memory address of the slice bs and converts it to anunsafe.Pointer
. Anunsafe.Pointer
is a special type in theunsafe
package that can hold the address of any type.(*string)(unsafe.Pointer(&bs))
converts theunsafe.Pointer
to a string pointer by performing type coercion. This creates a new string pointer that points to the same memory address as the slicebs
.*(*string)(unsafe.Pointer(&bs))
dereferences the string pointer to get the string value that it points to.
Prove it
This experiment shows the benchmark between the string(bs)
and our ByteSlice2String
function
var (
someBytes = []byte(`hello`)
str string
)
func BenchmarkByte2String(b *testing.B) {
b.Run("BuiltIn", func(b *testing.B) {
for i := 0; i < b.N; i++ {
str = BuiltIn(someBytes)
}
b.ReportAllocs()
})
b.Run("Byte2Slice", func(b *testing.B) {
for i := 0; i < b.N; i++ {
str = ByteSlice2String(someBytes)
}
b.ReportAllocs()
})
}
Benchmark result shows that the function ByteSlice2String
has zero allocation memory.
goos: darwin
goarch: amd64
pkg: github.com/ntk12/temp
cpu: Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
BenchmarkBuildString
BenchmarkBuildString/BuiltIn
BenchmarkBuildString/BuiltIn-16 75249337 15.88 ns/op 5 B/op 1 allocs/op
BenchmarkBuildString/Byte2Slice
BenchmarkBuildString/Byte2Slice-16 324706618 3.652 ns/op 0 B/op 0 allocs/op
PASS
ok github.com/ntk12/temp 4.461s
When this trick goes wrong
Using the unsafe
package and type coercion to convert a slice of bytes to a string can lead to unpredictable behavior and can compromise the safety guarantees provided by Go. It may also produce incorrect results if the slice of bytes does not have the correct memory layout or encoding
The new way in go 1.20
Go 1.20 adds SliceData
, String
, and StringData
which supports our cases in the CL. You can use the new function as follows:
func ByteSlice2String(b []byte) string {
if len(b) == 0 {
return ""
}
return unsafe.String(&b[0], len(b))
}
func String2ByteSlice(s string) []byte {
return unsafe.Slice(unsafe.StringData(s), len(s))
}