diff --git a/container/strmap/strmap.go b/container/strmap/strmap.go index a5fc83a..9631226 100644 --- a/container/strmap/strmap.go +++ b/container/strmap/strmap.go @@ -23,9 +23,9 @@ import ( "sort" "strings" - "github.com/cloudwego/gopkg/internal/hack" "github.com/cloudwego/gopkg/internal/hash/maphash" "github.com/cloudwego/gopkg/internal/strstore" + "github.com/cloudwego/gopkg/unsafex" ) // StrMap represents GC friendly readonly string map implementation. @@ -133,7 +133,7 @@ func (m *StrMap[V]) Len() int { // It panics if i is not in the range [0, Len()). func (m *StrMap[V]) Item(i int) (string, V) { e := &m.items[i] - return hack.ByteSliceToString(m.data[e.off : e.off+int(e.sz)]), e.v + return unsafex.BinaryToString(m.data[e.off : e.off+int(e.sz)]), e.v } type itemsBySlot[V any] []mapItem[V] diff --git a/container/strmap/strmap_test.go b/container/strmap/strmap_test.go index 7c56e01..c79d916 100644 --- a/container/strmap/strmap_test.go +++ b/container/strmap/strmap_test.go @@ -22,7 +22,7 @@ import ( "runtime" "testing" - "github.com/cloudwego/gopkg/internal/hack" + "github.com/cloudwego/gopkg/unsafex" "github.com/stretchr/testify/require" ) @@ -33,7 +33,7 @@ func randStrings(m, n int) []string { for i := 0; i < n; i++ { s := b[m*i:] s = s[:m] - ret = append(ret, hack.ByteSliceToString(s)) + ret = append(ret, unsafex.BinaryToString(s)) } return ret } diff --git a/protocol/thrift/binary.go b/protocol/thrift/binary.go index 21d391d..06f13af 100644 --- a/protocol/thrift/binary.go +++ b/protocol/thrift/binary.go @@ -23,7 +23,7 @@ import ( "unsafe" "github.com/bytedance/gopkg/lang/span" - "github.com/cloudwego/gopkg/internal/hack" + "github.com/cloudwego/gopkg/unsafex" ) var ( @@ -136,7 +136,7 @@ func (p BinaryProtocol) WriteStringNocopy(buf []byte, w NocopyWriter, v string) return p.WriteString(buf, v) } binary.BigEndian.PutUint32(buf, uint32(len(v))) - _ = w.WriteDirect(hack.StringToByteSlice(v), len(buf[4:])) // always err == nil ? + _ = w.WriteDirect(unsafex.StringToBinary(v), len(buf[4:])) // always err == nil ? return 4 } @@ -357,7 +357,7 @@ func (p BinaryProtocol) ReadString(buf []byte) (s string, l int, err error) { } if spanCacheEnable { data := spanCache.Copy(buf[4:l]) - s = hack.ByteSliceToString(data) + s = unsafex.BinaryToString(data) } else { s = string(buf[4:l]) } diff --git a/protocol/thrift/bufferreader.go b/protocol/thrift/bufferreader.go index f4d0105..b7b5c2b 100644 --- a/protocol/thrift/bufferreader.go +++ b/protocol/thrift/bufferreader.go @@ -24,7 +24,7 @@ import ( "github.com/bytedance/gopkg/lang/dirtmake" "github.com/cloudwego/gopkg/bufiox" - "github.com/cloudwego/gopkg/internal/hack" + "github.com/cloudwego/gopkg/unsafex" ) // BufferReader represents a reader for binary protocol @@ -162,7 +162,7 @@ func (r *BufferReader) ReadString() (s string, err error) { if err != nil { return "", err } - return hack.ByteSliceToString(b), nil + return unsafex.BinaryToString(b), nil } // ReadMessageBegin ... diff --git a/protocol/thrift/bufferwriter.go b/protocol/thrift/bufferwriter.go index ccdc1b5..37d0a72 100644 --- a/protocol/thrift/bufferwriter.go +++ b/protocol/thrift/bufferwriter.go @@ -22,7 +22,7 @@ import ( "sync" "github.com/cloudwego/gopkg/bufiox" - "github.com/cloudwego/gopkg/internal/hack" + "github.com/cloudwego/gopkg/unsafex" ) type BufferWriter struct { @@ -117,7 +117,7 @@ func (w *BufferWriter) WriteBinary(v []byte) error { } func (w *BufferWriter) WriteString(v string) error { - return w.WriteBinary(hack.StringToByteSlice(v)) + return w.WriteBinary(unsafex.StringToBinary(v)) } func (w *BufferWriter) WriteBool(v bool) error { diff --git a/protocol/ttheader/utils.go b/protocol/ttheader/utils.go index b9ee004..9d0f7f0 100644 --- a/protocol/ttheader/utils.go +++ b/protocol/ttheader/utils.go @@ -19,7 +19,7 @@ import ( "io" "github.com/cloudwego/gopkg/bufiox" - "github.com/cloudwego/gopkg/internal/hack" + "github.com/cloudwego/gopkg/unsafex" ) // The byte count of 32 and 16 integer values. @@ -109,7 +109,7 @@ func WriteString(val string, out bufiox.Writer) (int, error) { if err := WriteUint32(uint32(strLen), out); err != nil { return 0, err } - n, err := out.WriteBinary(hack.StringToByteSlice(val)) + n, err := out.WriteBinary(unsafex.StringToBinary(val)) if err != nil { return 0, err } @@ -122,7 +122,7 @@ func WriteString2BLen(val string, out bufiox.Writer) (int, error) { if err := WriteUint16(uint16(strLen), out); err != nil { return 0, err } - n, err := out.WriteBinary(hack.StringToByteSlice(val)) + n, err := out.WriteBinary(unsafex.StringToBinary(val)) if err != nil { return 0, err } diff --git a/internal/hack/hack.go b/unsafex/unsafex_go100.go similarity index 57% rename from internal/hack/hack.go rename to unsafex/unsafex_go100.go index c219209..c79ec42 100644 --- a/internal/hack/hack.go +++ b/unsafex/unsafex_go100.go @@ -1,3 +1,5 @@ +//go:build !go1.20 + /* * Copyright 2024 CloudWeGo Authors * @@ -14,33 +16,23 @@ * limitations under the License. */ -package hack +package unsafex import "unsafe" -type sliceHeader struct { - Data uintptr - Len int - Cap int -} - -type strHeader struct { - Data uintptr - Len int -} - -// ByteSliceToString converts []byte to string without copy -func ByteSliceToString(b []byte) string { +// BinaryToString converts []byte to string without copy +func BinaryToString(b []byte) string { return *(*string)(unsafe.Pointer(&b)) } -// StringToByteSlice converts string to []byte without copy -func StringToByteSlice(s string) []byte { - var v []byte - p0 := (*sliceHeader)(unsafe.Pointer(&v)) - p1 := (*strHeader)(unsafe.Pointer(&s)) - p0.Data = p1.Data - p0.Len = p1.Len - p0.Cap = p1.Len - return v +// StringToBinary converts string to []byte without copy +func StringToBinary(s string) (b []byte) { + type sliceHeader struct { + Data uintptr + Len int + Cap int + } + *(*string)(unsafe.Pointer(&b)) = s + (*sliceHeader)(unsafe.Pointer(&b)).Cap = len(s) + return } diff --git a/internal/hack/hack_test.go b/unsafex/unsafex_go120.go similarity index 55% rename from internal/hack/hack_test.go rename to unsafex/unsafex_go120.go index 749dd2f..64473d5 100644 --- a/internal/hack/hack_test.go +++ b/unsafex/unsafex_go120.go @@ -1,3 +1,5 @@ +//go:build go1.20 + /* * Copyright 2024 CloudWeGo Authors * @@ -14,17 +16,18 @@ * limitations under the License. */ -package hack +package unsafex -import ( - "testing" +import "unsafe" - "github.com/stretchr/testify/assert" -) +// BinaryToString converts []byte to string without copy +// Deprecated: use unsafe package instead if you're using >go1.20 +func BinaryToString(b []byte) string { + return unsafe.String(unsafe.SliceData(b), len(b)) +} -func TestUnsafe(t *testing.T) { - s := "hello" - b := []byte("hello") - assert.Equal(t, s, ByteSliceToString(b)) - assert.Equal(t, b, StringToByteSlice(s)) +// StringToBinary converts string to []byte without copy +// Deprecated: use unsafe package instead if you're using >go1.20 +func StringToBinary(s string) []byte { + return unsafe.Slice(unsafe.StringData(s), len(s)) } diff --git a/unsafex/unsafex_test.go b/unsafex/unsafex_test.go new file mode 100644 index 0000000..86468c4 --- /dev/null +++ b/unsafex/unsafex_test.go @@ -0,0 +1,55 @@ +/* + * Copyright 2024 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package unsafex + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestUBinaryToString(t *testing.T) { + b := []byte("hello") + s := BinaryToString(b) + assert.Equal(t, string(b), s) + b[0] = 'x' + assert.Equal(t, string(b), s) +} + +func BenchmarkBinaryToString(b *testing.B) { + x := []byte("hello") + for i := 0; i < b.N; i++ { + _ = BinaryToString(x) + } +} + +func TestStringToBinary(t *testing.T) { + x := []byte("hello") + // doesn't use string literal, or `b[0] = 'x'` will panic coz addr is readonly + s := string(x) + b := StringToBinary(s) + assert.Equal(t, s, string(b)) + b[0] = 'x' + assert.Equal(t, s, string(b)) +} + +func BenchmarkStringToBinary(b *testing.B) { + s := "hello" + for i := 0; i < b.N; i++ { + _ = StringToBinary(s) + } +}