Skip to content

Commit 2c93790

Browse files
authored
Merge pull request #809 from ydb-platform/perf-conversions
increase performance in str2bytes and bytes2str conversions
2 parents 5a33c7b + c9f639c commit 2c93790

File tree

11 files changed

+134
-34
lines changed

11 files changed

+134
-34
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
* Added `xstring.FromBytes([]byte)` and `xstring.ToBytes(string)` for fast conversion
2+
13
## v3.51.2
24
* Added `table/options.ReadFromSnapshot(bool)` option for `session.StreamReadTable()`
35

internal/decimal/decimal.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package decimal
33
import (
44
"math/big"
55
"math/bits"
6+
7+
"github.com/ydb-platform/ydb-go-sdk/v3/internal/xstring"
68
)
79

810
const wordSize = bits.UintSize / 8
@@ -253,7 +255,7 @@ func Format(x *big.Int, precision, scale uint32) string {
253255
bts[pos] = '-'
254256
}
255257

256-
return string(bts[pos:])
258+
return xstring.FromBytes(bts[pos:])
257259
}
258260

259261
// BigIntToByte returns the 16-byte array representation of x.

internal/secret/password.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
package secret
22

3+
import (
4+
"github.com/ydb-platform/ydb-go-sdk/v3/internal/xstring"
5+
)
6+
37
func Password(password string) string {
48
var (
59
bytes = []byte(password)
@@ -15,5 +19,5 @@ func Password(password string) string {
1519
bytes[i] = '*'
1620
}
1721
}
18-
return string(bytes)
22+
return xstring.FromBytes(bytes)
1923
}

internal/table/scanner/scan_raw.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212

1313
"github.com/ydb-platform/ydb-go-sdk/v3/internal/value"
1414
"github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors"
15+
"github.com/ydb-platform/ydb-go-sdk/v3/internal/xstring"
1516
"github.com/ydb-platform/ydb-go-sdk/v3/table/types"
1617
)
1718

@@ -226,12 +227,12 @@ func (s *rawConverter) YSON() (v []byte) {
226227

227228
func (s *rawConverter) JSON() (v []byte) {
228229
s.unwrap()
229-
return []byte(s.text())
230+
return xstring.ToBytes(s.text())
230231
}
231232

232233
func (s *rawConverter) JSONDocument() (v []byte) {
233234
s.unwrap()
234-
return []byte(s.text())
235+
return xstring.ToBytes(s.text())
235236
}
236237

237238
func (s *rawConverter) UUID() (v [16]byte) {

internal/table/scanner/scanner.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/ydb-platform/ydb-go-sdk/v3/internal/allocator"
1515
"github.com/ydb-platform/ydb-go-sdk/v3/internal/value"
1616
"github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors"
17+
"github.com/ydb-platform/ydb-go-sdk/v3/internal/xstring"
1718
"github.com/ydb-platform/ydb-go-sdk/v3/internal/xsync"
1819
"github.com/ydb-platform/ydb-go-sdk/v3/table/options"
1920
"github.com/ydb-platform/ydb-go-sdk/v3/table/result"
@@ -427,7 +428,7 @@ func (s *scanner) any() interface{} {
427428
value.TypeYSON,
428429
value.TypeJSON,
429430
value.TypeJSONDocument:
430-
return []byte(s.text())
431+
return xstring.ToBytes(s.text())
431432
default:
432433
_ = s.errorf(0, "unknown primitive types")
433434
return nil
@@ -680,11 +681,11 @@ func (s *scanner) setString(dst *string) {
680681
switch t := s.stack.current().t.GetTypeId(); t {
681682
case Ydb.Type_UUID:
682683
src := s.uint128()
683-
*dst = string(src[:])
684+
*dst = xstring.FromBytes(src[:])
684685
case Ydb.Type_UTF8, Ydb.Type_DYNUMBER, Ydb.Type_YSON, Ydb.Type_JSON, Ydb.Type_JSON_DOCUMENT:
685686
*dst = s.text()
686687
case Ydb.Type_STRING:
687-
*dst = string(s.bytes())
688+
*dst = xstring.FromBytes(s.bytes())
688689
default:
689690
_ = s.errorf(0, "scan row failed: incorrect source types %s", t)
690691
}
@@ -696,7 +697,7 @@ func (s *scanner) setByte(dst *[]byte) {
696697
src := s.uint128()
697698
*dst = src[:]
698699
case Ydb.Type_UTF8, Ydb.Type_DYNUMBER, Ydb.Type_YSON, Ydb.Type_JSON, Ydb.Type_JSON_DOCUMENT:
699-
*dst = []byte(s.text())
700+
*dst = xstring.ToBytes(s.text())
700701
case Ydb.Type_STRING:
701702
*dst = s.bytes()
702703
default:

internal/value/value.go

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/ydb-platform/ydb-go-sdk/v3/internal/allocator"
1616
"github.com/ydb-platform/ydb-go-sdk/v3/internal/decimal"
1717
"github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors"
18+
"github.com/ydb-platform/ydb-go-sdk/v3/internal/xstring"
1819
)
1920

2021
type Value interface {
@@ -122,7 +123,7 @@ func primitiveValueFromYDB(t PrimitiveType, v *Ydb.Value) (Value, error) {
122123
case TypeYSON:
123124
switch vv := v.GetValue().(type) {
124125
case *Ydb.Value_TextValue:
125-
return YSONValue([]byte(vv.TextValue)), nil
126+
return YSONValue(xstring.ToBytes(vv.TextValue)), nil
126127
case *Ydb.Value_BytesValue:
127128
return YSONValue(vv.BytesValue), nil
128129
default:
@@ -579,7 +580,7 @@ func (v *doubleValue) castTo(dst interface{}) error {
579580
*vv = strconv.FormatFloat(v.value, 'f', -1, 64)
580581
return nil
581582
case *[]byte:
582-
*vv = []byte(strconv.FormatFloat(v.value, 'f', -1, 64))
583+
*vv = xstring.ToBytes(strconv.FormatFloat(v.value, 'f', -1, 64))
583584
return nil
584585
case *float64:
585586
*vv = v.value
@@ -621,7 +622,7 @@ func (v dyNumberValue) castTo(dst interface{}) error {
621622
*vv = string(v)
622623
return nil
623624
case *[]byte:
624-
*vv = []byte(v)
625+
*vv = xstring.ToBytes(string(v))
625626
return nil
626627
default:
627628
return xerrors.WithStackTrace(fmt.Errorf("cannot cast '%+v' (type '%s') to '%T' destination", v, v.Type().Yql(), vv))
@@ -660,7 +661,7 @@ func (v *floatValue) castTo(dst interface{}) error {
660661
*vv = strconv.FormatFloat(float64(v.value), 'f', -1, 32)
661662
return nil
662663
case *[]byte:
663-
*vv = []byte(strconv.FormatFloat(float64(v.value), 'f', -1, 32))
664+
*vv = xstring.ToBytes(strconv.FormatFloat(float64(v.value), 'f', -1, 32))
664665
return nil
665666
case *float64:
666667
*vv = float64(v.value)
@@ -705,7 +706,7 @@ func (v int8Value) castTo(dst interface{}) error {
705706
*vv = strconv.FormatInt(int64(v), 10)
706707
return nil
707708
case *[]byte:
708-
*vv = []byte(strconv.FormatInt(int64(v), 10))
709+
*vv = xstring.ToBytes(strconv.FormatInt(int64(v), 10))
709710
return nil
710711
case *int64:
711712
*vv = int64(v)
@@ -760,7 +761,7 @@ func (v int16Value) castTo(dst interface{}) error {
760761
*vv = strconv.FormatInt(int64(v), 10)
761762
return nil
762763
case *[]byte:
763-
*vv = []byte(strconv.FormatInt(int64(v), 10))
764+
*vv = xstring.ToBytes(strconv.FormatInt(int64(v), 10))
764765
return nil
765766
case *int64:
766767
*vv = int64(v)
@@ -812,7 +813,7 @@ func (v int32Value) castTo(dst interface{}) error {
812813
*vv = strconv.FormatInt(int64(v), 10)
813814
return nil
814815
case *[]byte:
815-
*vv = []byte(strconv.FormatInt(int64(v), 10))
816+
*vv = xstring.ToBytes(strconv.FormatInt(int64(v), 10))
816817
return nil
817818
case *int64:
818819
*vv = int64(v)
@@ -864,7 +865,7 @@ func (v int64Value) castTo(dst interface{}) error {
864865
*vv = strconv.FormatInt(int64(v), 10)
865866
return nil
866867
case *[]byte:
867-
*vv = []byte(strconv.FormatInt(int64(v), 10))
868+
*vv = xstring.ToBytes(strconv.FormatInt(int64(v), 10))
868869
return nil
869870
case *int64:
870871
*vv = int64(v)
@@ -985,7 +986,7 @@ func (v jsonValue) castTo(dst interface{}) error {
985986
*vv = string(v)
986987
return nil
987988
case *[]byte:
988-
*vv = []byte(v)
989+
*vv = xstring.ToBytes(string(v))
989990
return nil
990991
default:
991992
return xerrors.WithStackTrace(fmt.Errorf("cannot cast '%+v' (type '%s') to '%T' destination", v, v.Type().Yql(), vv))
@@ -1022,7 +1023,7 @@ func (v jsonDocumentValue) castTo(dst interface{}) error {
10221023
*vv = string(v)
10231024
return nil
10241025
case *[]byte:
1025-
*vv = []byte(v)
1026+
*vv = xstring.ToBytes(string(v))
10261027
return nil
10271028
default:
10281029
return xerrors.WithStackTrace(fmt.Errorf("cannot cast '%+v' (type '%s') to '%T' destination", v, v.Type().Yql(), vv))
@@ -1401,7 +1402,7 @@ func (v tzDateValue) castTo(dst interface{}) error {
14011402
*vv = string(v)
14021403
return nil
14031404
case *[]byte:
1404-
*vv = []byte(v)
1405+
*vv = xstring.ToBytes(string(v))
14051406
return nil
14061407
default:
14071408
return xerrors.WithStackTrace(fmt.Errorf("cannot cast '%+v' (type '%s') to '%T' destination", v, v.Type().Yql(), vv))
@@ -1442,7 +1443,7 @@ func (v tzDatetimeValue) castTo(dst interface{}) error {
14421443
*vv = string(v)
14431444
return nil
14441445
case *[]byte:
1445-
*vv = []byte(v)
1446+
*vv = xstring.ToBytes(string(v))
14461447
return nil
14471448
default:
14481449
return xerrors.WithStackTrace(fmt.Errorf("cannot cast '%+v' (type '%s') to '%T' destination", v, v.Type().Yql(), vv))
@@ -1483,7 +1484,7 @@ func (v tzTimestampValue) castTo(dst interface{}) error {
14831484
*vv = string(v)
14841485
return nil
14851486
case *[]byte:
1486-
*vv = []byte(v)
1487+
*vv = xstring.ToBytes(string(v))
14871488
return nil
14881489
default:
14891490
return xerrors.WithStackTrace(fmt.Errorf("cannot cast '%+v' (type '%s') to '%T' destination", v, v.Type().Yql(), vv))
@@ -1524,7 +1525,7 @@ func (v uint8Value) castTo(dst interface{}) error {
15241525
*vv = strconv.FormatInt(int64(v), 10)
15251526
return nil
15261527
case *[]byte:
1527-
*vv = []byte(strconv.FormatInt(int64(v), 10))
1528+
*vv = xstring.ToBytes(strconv.FormatInt(int64(v), 10))
15281529
return nil
15291530
case *uint64:
15301531
*vv = uint64(v)
@@ -1588,7 +1589,7 @@ func (v uint16Value) castTo(dst interface{}) error {
15881589
*vv = strconv.FormatInt(int64(v), 10)
15891590
return nil
15901591
case *[]byte:
1591-
*vv = []byte(strconv.FormatInt(int64(v), 10))
1592+
*vv = xstring.ToBytes(strconv.FormatInt(int64(v), 10))
15921593
return nil
15931594
case *uint64:
15941595
*vv = uint64(v)
@@ -1646,7 +1647,7 @@ func (v uint32Value) castTo(dst interface{}) error {
16461647
*vv = strconv.FormatInt(int64(v), 10)
16471648
return nil
16481649
case *[]byte:
1649-
*vv = []byte(strconv.FormatInt(int64(v), 10))
1650+
*vv = xstring.ToBytes(strconv.FormatInt(int64(v), 10))
16501651
return nil
16511652
case *uint64:
16521653
*vv = uint64(v)
@@ -1695,7 +1696,7 @@ func (v uint64Value) castTo(dst interface{}) error {
16951696
*vv = strconv.FormatInt(int64(v), 10)
16961697
return nil
16971698
case *[]byte:
1698-
*vv = []byte(strconv.FormatInt(int64(v), 10))
1699+
*vv = xstring.ToBytes(strconv.FormatInt(int64(v), 10))
16991700
return nil
17001701
case *uint64:
17011702
*vv = uint64(v)
@@ -1735,7 +1736,7 @@ func (v textValue) castTo(dst interface{}) error {
17351736
*vv = string(v)
17361737
return nil
17371738
case *[]byte:
1738-
*vv = []byte(v)
1739+
*vv = xstring.ToBytes(string(v))
17391740
return nil
17401741
default:
17411742
return xerrors.WithStackTrace(fmt.Errorf("cannot cast '%+v' (type '%s') to '%T' destination", v, v.Type().Yql(), vv))
@@ -1947,7 +1948,7 @@ type ysonValue []byte
19471948
func (v ysonValue) castTo(dst interface{}) error {
19481949
switch vv := dst.(type) {
19491950
case *string:
1950-
*vv = string(v)
1951+
*vv = xstring.FromBytes(v)
19511952
return nil
19521953
case *[]byte:
19531954
*vv = v
@@ -2122,7 +2123,7 @@ type bytesValue []byte
21222123
func (v bytesValue) castTo(dst interface{}) error {
21232124
switch vv := dst.(type) {
21242125
case *string:
2125-
*vv = string(v)
2126+
*vv = xstring.FromBytes(v)
21262127
return nil
21272128
case *[]byte:
21282129
*vv = v

internal/xstring/convert.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package xstring
2+
3+
import (
4+
"reflect"
5+
"unsafe"
6+
)
7+
8+
func FromBytes(b []byte) string {
9+
return *(*string)(unsafe.Pointer(&b))
10+
}
11+
12+
func ToBytes(s string) (b []byte) {
13+
pb := (*reflect.SliceHeader)(unsafe.Pointer(&b))
14+
ps := (*reflect.StringHeader)(unsafe.Pointer(&s))
15+
pb.Data = ps.Data
16+
pb.Len = ps.Len
17+
pb.Cap = ps.Len
18+
19+
return b
20+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//nolint:goconst
2+
package xstring
3+
4+
import (
5+
"testing"
6+
)
7+
8+
// Test the performance of the standard conversion string()
9+
func Benchmark_StdFromBytes(b *testing.B) {
10+
b.ReportAllocs()
11+
x := []byte("Hello world! Hello world! Hello world!")
12+
for i := 0; i < b.N; i++ {
13+
_ = string(x)
14+
}
15+
}
16+
17+
// Test the performance of strong conversion []byte to string
18+
func Benchmark_FromBytes(b *testing.B) {
19+
b.ReportAllocs()
20+
x := []byte("Hello world! Hello world! Hello world!")
21+
for i := 0; i < b.N; i++ {
22+
_ = FromBytes(x)
23+
}
24+
}
25+
26+
// Test the performance of standard conversion []byte
27+
func Benchmark_StdToBytes(b *testing.B) {
28+
b.ReportAllocs()
29+
x := "Hello world! Hello world! Hello world!"
30+
for i := 0; i < b.N; i++ {
31+
_ = []byte(x)
32+
}
33+
}
34+
35+
// Test the performance of strong conversion string to []byte
36+
func Benchmark_ToBytes(b *testing.B) {
37+
b.ReportAllocs()
38+
x := "Hello world! Hello world! Hello world!"
39+
for i := 0; i < b.N; i++ {
40+
_ = ToBytes(x)
41+
}
42+
}

internal/xstring/convert_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package xstring
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestBytesToString(t *testing.T) {
10+
data := []byte("Hello world!")
11+
got := FromBytes(data)
12+
want := string(data)
13+
14+
assert.Equal(t, want, got)
15+
}
16+
17+
func TestStringToBytes(t *testing.T) {
18+
data := "Hello world!"
19+
got := ToBytes(data)
20+
want := []byte(data)
21+
22+
assert.Equal(t, want, got)
23+
}

log/field.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99

1010
"github.com/ydb-platform/ydb-go-sdk/v3/internal/allocator"
1111
"github.com/ydb-platform/ydb-go-sdk/v3/internal/version"
12+
"github.com/ydb-platform/ydb-go-sdk/v3/internal/xstring"
1213
"github.com/ydb-platform/ydb-go-sdk/v3/trace"
1314
)
1415

@@ -363,7 +364,7 @@ func (m metadata) String() string {
363364
if err != nil {
364365
return fmt.Sprintf("error:%s", err)
365366
}
366-
return string(b)
367+
return xstring.FromBytes(b)
367368
}
368369

369370
func appendFieldByCondition(condition bool, ifTrueField Field, fields ...Field) []Field {

0 commit comments

Comments
 (0)