-
Notifications
You must be signed in to change notification settings - Fork 18.3k
Description
Go version
go version go1.24.0 darwin/arm64
Output of go env
in your module/workspace:
AR='ar'
CC='clang'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='clang++'
GCCGO='gccgo'
GO111MODULE=''
GOARCH='arm64'
GOARM64='v8.0'
GOAUTH='netrc'
GOBIN='/Users/yyy/github/bin'
GOCACHE='/Users/yyy/Library/Caches/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/Users/yyy/Library/Application Support/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=/var/folders/th/45dyv7rs03g5t8v9clfswcp80000gn/T/go-build3229043422=/tmp/go-build -gno-record-gcc-switches -fno-common'
GOHOSTARCH='arm64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMOD='/Users/yyy/github/src/github.com/katydid/parser-go-json/go.mod'
GOMODCACHE='/Users/yyy/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='darwin'
GOPATH='/Users/yyy/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/usr/local/go'
GOSUMDB='sum.golang.org'
GOTELEMETRY='local'
GOTELEMETRYDIR='/Users/yyy/Library/Application Support/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/usr/local/go/pkg/tool/darwin_arm64'
GOVCS=''
GOVERSION='go1.24.0'
GOWORK=''
PKG_CONFIG='pkg-config'
What did you do?
I see reflect.SliceHeader is deprecated and I am trying to upgrade to unsafe.Slice.
I want to use it to cast from an int64 to a slice of bytes, without allocating any memory on the heap.
I tried upgrading to unsafe.Slice, but it seems that the pointer is leaked to the heap, which is usually not the case with reflect.SliceHeader.
Here is a small reproduction:
$ go build -gcflags "-m -l" main.go
# command-line-arguments
./main.go:10:13: ... argument does not escape
./main.go:10:14: "hello" escapes to heap
./main.go:16:35: &reflect.SliceHeader{...} does not escape
./main.go:24:26: moved to heap: i
./main.go:29:13: make([]byte, 8) escapes to heap
$ cat -n main.go
1 package main
2
3 import (
4 "fmt"
5 "reflect"
6 "unsafe"
7 )
8
9 func main() {
10 fmt.Println("hello")
11
12 }
13
14 // Doesn't allocate, but is deprecated
15 func castFromInt64(i int64) []byte {
16 return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
17 Len: 8,
18 Cap: 8,
19 Data: uintptr(unsafe.Pointer(&i)),
20 }))
21 }
22
23 // Allocates, so need to keep using deprecated method
24 func unsafeCastFromInt64(i int64) []byte {
25 return unsafe.Slice((*byte)(unsafe.Pointer(&i)), 8)
26 }
27
28 func takeint(i int64) []byte {
29 return make([]byte, 8)
30 }
I can't seem to record an allocation in isolation, so maybe I did something else wrong here:
func TestAllocsUnsafeCast(t *testing.T) {
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1))
f := func() {
want := int64(math.MaxInt64)
bs := unsafeCastFromInt64(want)
got := castToInt64(bs)
if got != want {
t.Fatalf("want %d got %d", want, got)
}
}
allocs := testing.AllocsPerRun(1000, f)
if allocs > 0 {
t.Fatalf("UnsafeCast Allocs = %f", allocs)
}
}
But I can reproduce it, if I use it in a larger program and make a single change:
katydid/parser-go-json@16defb4
$ cd json/parse
$ go test -run=TestNoAllocsOnAverage -v
=== RUN TestNoAllocsOnAverage
alloc_test.go:27: seed = 1741363591861790000, got 3 allocs, want 0 allocs
--- FAIL: TestNoAllocsOnAverage (0.04s)
FAIL
exit status 1
The assembler analysis is also interesting and shows that it is significantly more instructions to use unsafe.Slice that to use reflect.SliceHeader for doing this type of cast: https://godbolt.org/z/azPebxG79
So I am just wondering if there is better way to cast from int64 to a slice of bytes without allocating any memoory?
Or is there a concern about the performance implications of moving from reflect.SliceHeader to unsafe.Slice?
I am also wondering if there is a new way to cast from a byte slice to string without doing a copy?
This is the old way, that I used to do it:
func castToString(buf []byte) string {
header := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
strHeader := reflect.StringHeader{Data: header.Data, Len: header.Len}
return *(*string)(unsafe.Pointer(&strHeader))
}
What did you see happen?
This is also answered in previous question.
What did you expect to see?
This is also answered in previous question.