Skip to content

Commit

Permalink
feat: new unsafex pkg
Browse files Browse the repository at this point in the history
  • Loading branch information
xiaost committed Nov 26, 2024
1 parent b8f4557 commit acb7e12
Show file tree
Hide file tree
Showing 9 changed files with 97 additions and 47 deletions.
4 changes: 2 additions & 2 deletions container/strmap/strmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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]
Expand Down
4 changes: 2 additions & 2 deletions container/strmap/strmap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import (
"runtime"
"testing"

"github.com/cloudwego/gopkg/internal/hack"
"github.com/cloudwego/gopkg/unsafex"
"github.com/stretchr/testify/require"
)

Expand All @@ -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
}
Expand Down
6 changes: 3 additions & 3 deletions protocol/thrift/binary.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (
"unsafe"

"github.com/bytedance/gopkg/lang/span"
"github.com/cloudwego/gopkg/internal/hack"
"github.com/cloudwego/gopkg/unsafex"
)

var (
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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])
}
Expand Down
4 changes: 2 additions & 2 deletions protocol/thrift/bufferreader.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 ...
Expand Down
4 changes: 2 additions & 2 deletions protocol/thrift/bufferwriter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
6 changes: 3 additions & 3 deletions protocol/ttheader/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
}
Expand All @@ -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.BinaryToString(val))

Check failure on line 125 in protocol/ttheader/utils.go

View workflow job for this annotation

GitHub Actions / unit-benchmark-test (1.18, X64)

cannot use unsafex.BinaryToString(val) (value of type string) as type []byte in argument to out.WriteBinary

Check failure on line 125 in protocol/ttheader/utils.go

View workflow job for this annotation

GitHub Actions / unit-benchmark-test (1.18, X64)

cannot use val (variable of type string) as type []byte in argument to unsafex.BinaryToString

Check failure on line 125 in protocol/ttheader/utils.go

View workflow job for this annotation

GitHub Actions / unit-benchmark-test (1.19, X64)

cannot use unsafex.BinaryToString(val) (value of type string) as type []byte in argument to out.WriteBinary

Check failure on line 125 in protocol/ttheader/utils.go

View workflow job for this annotation

GitHub Actions / unit-benchmark-test (1.19, X64)

cannot use val (variable of type string) as type []byte in argument to unsafex.BinaryToString

Check failure on line 125 in protocol/ttheader/utils.go

View workflow job for this annotation

GitHub Actions / unit-benchmark-test (1.21, X64)

cannot use unsafex.BinaryToString(val) (value of type string) as []byte value in argument to out.WriteBinary

Check failure on line 125 in protocol/ttheader/utils.go

View workflow job for this annotation

GitHub Actions / unit-benchmark-test (1.21, X64)

cannot use val (variable of type string) as []byte value in argument to unsafex.BinaryToString

Check failure on line 125 in protocol/ttheader/utils.go

View workflow job for this annotation

GitHub Actions / unit-benchmark-test (1.22, X64)

cannot use unsafex.BinaryToString(val) (value of type string) as []byte value in argument to out.WriteBinary

Check failure on line 125 in protocol/ttheader/utils.go

View workflow job for this annotation

GitHub Actions / unit-benchmark-test (1.22, X64)

cannot use val (variable of type string) as []byte value in argument to unsafex.BinaryToString

Check failure on line 125 in protocol/ttheader/utils.go

View workflow job for this annotation

GitHub Actions / unit-benchmark-test (1.23, X64)

cannot use unsafex.BinaryToString(val) (value of type string) as []byte value in argument to out.WriteBinary

Check failure on line 125 in protocol/ttheader/utils.go

View workflow job for this annotation

GitHub Actions / unit-benchmark-test (1.23, X64)

cannot use val (variable of type string) as []byte value in argument to unsafex.BinaryToString
if err != nil {
return 0, err
}
Expand Down
38 changes: 15 additions & 23 deletions internal/hack/hack.go → unsafex/unsafex_go100.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//go:build !go1.20

/*
* Copyright 2024 CloudWeGo Authors
*
Expand All @@ -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
}
23 changes: 13 additions & 10 deletions internal/hack/hack_test.go → unsafex/unsafex_go120.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//go:build go1.20

/*
* Copyright 2024 CloudWeGo Authors
*
Expand All @@ -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))

Check failure on line 26 in unsafex/unsafex_go120.go

View workflow job for this annotation

GitHub Actions / unit-benchmark-test (1.20, X64)

unsafe.SliceData requires go1.20 or later (-lang was set to go1.18; check go.mod)
}

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))

Check failure on line 32 in unsafex/unsafex_go120.go

View workflow job for this annotation

GitHub Actions / unit-benchmark-test (1.20, X64)

unsafe.StringData requires go1.20 or later (-lang was set to go1.18; check go.mod)
}
55 changes: 55 additions & 0 deletions unsafex/unsafex_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}

0 comments on commit acb7e12

Please sign in to comment.