kubernetes / kubernetes

Production-Grade Container Scheduling and Management

Home Page:https://kubernetes.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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:

  • // toBytes performs unholy acts to avoid allocations
    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))
    }

  • // toBytes performs unholy acts to avoid allocations
    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))
    }

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