Skip to content

Commit d56136b

Browse files
committed
opt:(encoder) use std strconv.AppendInt for better performance on arm
1 parent d89a341 commit d56136b

File tree

4 files changed

+822
-15
lines changed

4 files changed

+822
-15
lines changed

internal/encoder/alg/spec.go

+3-15
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ package alg
2121

2222
import (
2323
"runtime"
24+
"strconv"
2425
"unsafe"
2526

2627
"github.com/bytedance/sonic/internal/native"
@@ -177,22 +178,9 @@ func F32toa(buf []byte, v float32) ([]byte) {
177178
}
178179

179180
func I64toa(buf []byte, v int64) ([]byte) {
180-
buf = rt.GuardSlice2(buf, 32)
181-
ret := native.I64toa((*byte)(rt.IndexByte(buf, len(buf))), v)
182-
if ret > 0 {
183-
return buf[:len(buf)+ret]
184-
} else {
185-
return buf
186-
}
181+
return strconv.AppendInt(buf, v, 10)
187182
}
188183

189184
func U64toa(buf []byte, v uint64) ([]byte) {
190-
buf = rt.GuardSlice2(buf, 32)
191-
ret := native.U64toa((*byte)(rt.IndexByte(buf, len(buf))), v)
192-
if ret > 0 {
193-
return buf[:len(buf)+ret]
194-
} else {
195-
return buf
196-
}
185+
return strconv.AppendUint(buf, v, 10)
197186
}
198-

internal/encoder/alg/spec_test.go

+187
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
/**
2+
* Copyright 2025 ByteDance Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package alg
18+
19+
import (
20+
"bytes"
21+
"encoding/json"
22+
"math/rand"
23+
"strconv"
24+
"strings"
25+
"testing"
26+
27+
"github.com/bytedance/sonic/testdata"
28+
)
29+
30+
func BenchmarkU64toa(b *testing.B) {
31+
b.ReportAllocs()
32+
buf := make([]byte, 0, 64)
33+
for x :=0 ;x <= 62; x+=4 {
34+
d := 1<<x
35+
b.Run("sonic-"+strconv.Itoa(d), func(b *testing.B) {
36+
b.ResetTimer()
37+
for i := 0; i < b.N; i++ {
38+
_ = U64toa(buf, uint64(d))
39+
}
40+
})
41+
b.Run("std-"+strconv.Itoa(d), func(b *testing.B) {
42+
b.ResetTimer()
43+
for i := 0; i < b.N; i++ {
44+
_ = strconv.AppendUint(buf, uint64(d), 10)
45+
}
46+
})
47+
}
48+
}
49+
50+
func BenchmarkI64toa(b *testing.B) {
51+
b.ReportAllocs()
52+
buf := make([]byte, 0, 64)
53+
for x :=0 ;x <= 62; x+=4 {
54+
d := 1<<x
55+
b.Run("sonic-"+strconv.Itoa(d), func(b *testing.B) {
56+
b.ResetTimer()
57+
for i := 0; i < b.N; i++ {
58+
_ = I64toa(buf, int64(d))
59+
}
60+
})
61+
b.Run("std-"+strconv.Itoa(d), func(b *testing.B) {
62+
b.ResetTimer()
63+
for i := 0; i < b.N; i++ {
64+
_ = strconv.AppendInt(buf, int64(d), 10)
65+
}
66+
})
67+
}
68+
}
69+
70+
func BenchmarkF64toa(b *testing.B) {
71+
b.ReportAllocs()
72+
buf := make([]byte, 0, 64)
73+
for x :=0 ;x <= 62; x+=4 {
74+
d := 1<<x
75+
f := float64(d)+rand.Float64()
76+
b.Run("sonic-"+strconv.FormatFloat(f, 'g', -1, 64), func(b *testing.B) {
77+
b.ResetTimer()
78+
for i := 0; i < b.N; i++ {
79+
_ = F64toa(buf, f)
80+
}
81+
})
82+
b.Run("std-"+strconv.FormatFloat(f, 'g', -1, 64), func(b *testing.B) {
83+
b.ResetTimer()
84+
for i := 0; i < b.N; i++ {
85+
_ = strconv.AppendFloat(buf, f, 'g', -1, 64)
86+
}
87+
})
88+
}
89+
}
90+
91+
func BenchmarkF32toa(b *testing.B) {
92+
b.ReportAllocs()
93+
buf := make([]byte, 0, 64)
94+
for x :=0 ;x <= 30; x+=2 {
95+
d := 1<<x
96+
f := float32(d)+rand.Float32()
97+
b.Run("sonic-"+strconv.FormatFloat(float64(f), 'g', -1, 32), func(b *testing.B) {
98+
b.ResetTimer()
99+
for i := 0; i < b.N; i++ {
100+
_ = F32toa(buf, f)
101+
}
102+
})
103+
b.Run("std-"+strconv.FormatFloat(float64(f), 'g', -1, 32), func(b *testing.B) {
104+
b.ResetTimer()
105+
for i := 0; i < b.N; i++ {
106+
_ = strconv.AppendFloat(buf, float64(f), 'g', -1, 32)
107+
}
108+
})
109+
}
110+
}
111+
112+
func BenchmarkQuote(b *testing.B) {
113+
b.ReportAllocs()
114+
var runner = func(seed string) func(b *testing.B) {
115+
return func(b *testing.B) {
116+
buf := make([]byte, 0, len(seed)*1024*1024)
117+
for l := 1; l< cap(buf)*10; l*=10 {
118+
src := strings.Repeat(seed, l)
119+
b.Run("sonic-"+strconv.Itoa(len(src)), func(b *testing.B) {
120+
b.ResetTimer()
121+
for i:=0 ; i < b.N; i++ {
122+
_ = Quote(buf, src, false)
123+
}
124+
})
125+
b.Run("std-"+strconv.Itoa(len(src)), func(b *testing.B) {
126+
b.ResetTimer()
127+
for i:=0 ; i < b.N; i++ {
128+
_ = strconv.AppendQuote(buf, src)
129+
}
130+
})
131+
}
132+
}
133+
}
134+
135+
b.Run("no quote", runner("abcdefghij"))
136+
b.Run("1/10 quote", runner("abcdefghi\n"))
137+
b.Run("1/5 quote", runner("abcd\nfghi\n"))
138+
}
139+
140+
func BenchmarkValid(b *testing.B) {
141+
b.ReportAllocs()
142+
var runner = func(seed []byte) func(b *testing.B) {
143+
return func(b *testing.B) {
144+
b.Run("sonic", func(b *testing.B) {
145+
b.ResetTimer()
146+
for i:=0 ; i < b.N; i++ {
147+
_, _ = Valid(seed)
148+
}
149+
})
150+
b.Run("std", func(b *testing.B) {
151+
b.ResetTimer()
152+
for i:=0 ; i < b.N; i++ {
153+
_ = json.Valid(seed)
154+
}
155+
})
156+
}
157+
}
158+
b.Run("valid-small", runner([]byte(`{"a":1}`)))
159+
b.Run("invalid-small", runner([]byte(`{"a":1>`)))
160+
b.Run("valid-large", runner([]byte(testdata.TwitterJson)))
161+
b.Run("invalid-large", runner([]byte(strings.ReplaceAll(testdata.TwitterJson, "}", ">"))))
162+
}
163+
164+
func BenchmarkEscapeHTML(b *testing.B) {
165+
b.ReportAllocs()
166+
var runner = func(seed []byte) func(b *testing.B) {
167+
return func(b *testing.B) {
168+
buf := make([]byte, 0, len(seed)*10)
169+
b.Run("sonic", func(b *testing.B) {
170+
b.ResetTimer()
171+
for i:=0 ; i < b.N; i++ {
172+
_ = HtmlEscape(buf, seed)
173+
}
174+
})
175+
b.Run("std", func(b *testing.B) {
176+
b.ResetTimer()
177+
for i:=0 ; i < b.N; i++ {
178+
bf := bytes.NewBuffer(buf)
179+
json.HTMLEscape(bf, seed)
180+
}
181+
})
182+
}
183+
}
184+
185+
b.Run("small", runner([]byte(`{"a":"<>"}`)))
186+
b.Run("large", runner([]byte(testdata.TwitterJson)))
187+
}

issue_test/biz_test.go

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/**
2+
* Copyright 2025 ByteDance Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package issue_test
18+
19+
import (
20+
"encoding/json"
21+
"testing"
22+
23+
"github.com/bytedance/sonic"
24+
)
25+
26+
type User struct {
27+
Name string
28+
Age int
29+
}
30+
31+
var user = User{Name: "test", Age: 18}
32+
var data []byte
33+
34+
// Benchmark Sonic serialization
35+
func BenchmarkSonicMarshal(b *testing.B) {
36+
b.ReportAllocs()
37+
for i := 0; i < b.N; i++ {
38+
_, err := sonic.Marshal(user)
39+
if err != nil {
40+
b.Fatal(err)
41+
}
42+
}
43+
}
44+
45+
// Benchmark Sonic deserialization
46+
func BenchmarkSonicUnmarshal(b *testing.B) {
47+
data, _ = sonic.Marshal(user)
48+
b.ReportAllocs()
49+
for i := 0; i < b.N; i++ {
50+
var newUser User
51+
err := sonic.Unmarshal(data, &newUser)
52+
if err != nil {
53+
b.Fatal(err)
54+
}
55+
}
56+
}
57+
58+
// Benchmark standard JSON serialization
59+
func BenchmarkStandardMarshal(b *testing.B) {
60+
b.ReportAllocs()
61+
for i := 0; i < b.N; i++ {
62+
_, err := json.Marshal(user)
63+
if err != nil {
64+
b.Fatal(err)
65+
}
66+
}
67+
}
68+
69+
// Benchmark standard JSON deserialization
70+
func BenchmarkStandardUnmarshal(b *testing.B) {
71+
data, _ = json.Marshal(user)
72+
b.ReportAllocs()
73+
for i := 0; i < b.N; i++ {
74+
var newUser User
75+
err := json.Unmarshal(data, &newUser)
76+
if err != nil {
77+
b.Fatal(err)
78+
}
79+
}
80+
}

0 commit comments

Comments
 (0)