As of go 1.22, there's no need to use the unsafe package for string to bytes conversion
Aden-Q opened this issue · comments
What would you like to be added?
As of go 1.22, for string to bytes conversion, we can replace the usage of unsafe.Slice(unsafe.StringData(s), len(s))
with type casting []bytes(str)
, without the worry of losing performance.
As of go 1.22, string to bytes conversion []bytes(str)
is faster than using the unsafe
package. Both methods have 0 memory allocation now.
I saw at least two places in the codebase still using the unsafe
way:
Here's my benchmark results, comparing two ways of conversion:
╰─○ go test -v -run=none -bench=. -benchmem=true
goos: darwin
goarch: amd64
pkg: example.com/m/v2
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
BenchmarkStringToBytesUnsafe
BenchmarkStringToBytesUnsafe-12 1000000000 0.5636 ns/op 0 B/op 0 allocs/op
BenchmarkStringToBytesCasting
BenchmarkStringToBytesCasting-12 1000000000 0.2548 ns/op 0 B/op 0 allocs/op
PASS
ok example.com/m/v2 1.448s
My test code:
package main
import (
"testing"
"unsafe"
)
const (
str = "some random string"
)
func toBytes(s string) []byte {
// unsafe.StringData is unspecified for the empty string, so we provide a strict interpretation
if len(s) == 0 {
return nil
}
// Copied from go 1.20.1 os.File.WriteString
// https://github.com/golang/go/blob/202a1a57064127c3f19d96df57b9f9586145e21c/src/os/file.go#L246
return unsafe.Slice(unsafe.StringData(s), len(s))
}
func toBytesRaw(s string) []byte {
return []byte(s)
}
func BenchmarkStringToBytesUnsafe(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = toBytes(str)
}
}
func BenchmarkStringToBytesCasting(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = toBytesRaw(str)
}
}
Why is this needed?
- Kubernetes as of now is built with go 1.22, in go 1.22 type casting is faster than unsafe, regarding only string to bytes conversion
- We can make string to bytes conversion faster with type casting
- We don't need to be 'unsafe' anymore
This issue is currently awaiting triage.
If a SIG or subproject determines this is a relevant issue, they will accept it by applying the triage/accepted
label and provide further guidance.
The triage/accepted
label can be added by org members by writing /triage accepted
in a comment.
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.
/sig api-machinery
Good catch! Why should we use unsafe packages before?
@cyclinder For string to bytes conversion, before go 1.22, type casting is not 0 memory allocation, and indeed is slower than the unsafe way:
go 1.21 (or before):
╰─± go test -run=xxx -bench=. ./...
goos: darwin
goarch: amd64
pkg: github.com/josestg/zerocast
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
BenchmarkStringToBytesUnsafe-10 7207893 151.1 ns/op 1792 B/op 1 allocs/op
BenchmarkStringToBytesTypeCasting-10 1000000000 0.3904 ns/op 0 B/op 0 allocs/op
but not the case for go 1.22, type casting is 0 memory allocation now, faster and safer:
╰─± go test -run=xxx -bench=. ./...
goos: darwin
goarch: amd64
pkg: github.com/josestg/zerocast
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
BenchmarkStringToBytesUnsafe-12 1000000000 0.2581 ns/op 0 B/op 0 allocs/op
BenchmarkStringToBytesTypeCasting-12 1000000000 0.4422 ns/op 0 B/op 0 allocs/op