Skip to content

Commit 96a4279

Browse files
committed
speed up marshaller
1 parent 0f062c1 commit 96a4279

File tree

5 files changed

+190
-45
lines changed

5 files changed

+190
-45
lines changed

README.md

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,34 @@ import bencode "github.com/IncSW/go-bencode"
1616
## Quick Start
1717

1818
```go
19-
data, err := bencode.Marshal(value)
19+
var dict interface{} = map[string]interface{}{
20+
"int": 123,
21+
"string": "Hello, World",
22+
"list": []interface{}{"foo", "bar"},
23+
}
24+
data, err := bencode.Marshal(dict)
25+
if err != nil {
26+
panic(err)
27+
}
28+
fmt.Println(string(data))
29+
30+
// Output:
31+
// d3:inti123e4:listl3:foo3:bare6:string12:Hello, Worlde
2032
```
2133

2234
```go
2335
data, err := bencode.Unmarshal(value)
2436
```
2537

26-
## Performance
38+
## Performance [benchmarks](https://github.com/IncSW/go-bencode/tree/benchmarks/benchmarks)
2739

2840
### Go 1.16, Debian 9.1, i7-7700
2941

3042
### Marshal
3143

3244
| Library | Time | Bytes Allocated | Objects Allocated |
3345
| :------------------ | :---------: | :-------------: | :---------------: |
34-
| IncSW/go-bencode | 795.9 ns/op | 176 B/op | 6 allocs/op |
46+
| IncSW/go-bencode | 614.4 ns/op | 112 B/op | 2 allocs/op |
3547
| marksamman/bencode | 820.3 ns/op | 384 B/op | 8 allocs/op |
3648
| cristalhq/bencode | 994.2 ns/op | 928 B/op | 4 allocs/op |
3749
| aleksatr/go-bencode | 1061 ns/op | 736 B/op | 9 allocs/op |

example_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package bencode_test
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/IncSW/go-bencode"
8+
)
9+
10+
func TestExample(t *testing.T) {
11+
var dict interface{} = map[string]interface{}{
12+
"int": 123,
13+
"string": "Hello, World",
14+
"list": []interface{}{"foo", "bar"},
15+
}
16+
data, err := bencode.Marshal(dict)
17+
if err != nil {
18+
panic(err)
19+
}
20+
fmt.Println(string(data))
21+
}

marshaler.go

Lines changed: 112 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,111 @@
11
package bencode
22

3-
import (
4-
"fmt"
5-
"sort"
6-
"strconv"
7-
)
3+
import "fmt"
4+
5+
func prepareBuffer(result *[]byte, offset int, length int, neededLength int) int {
6+
availableLength := length - offset
7+
if availableLength >= neededLength {
8+
return length
9+
}
10+
11+
rate := 1
12+
for availableLength < neededLength {
13+
rate++
14+
availableLength = length*rate - offset
15+
}
16+
17+
newResult := make([]byte, length*rate)
18+
copy(newResult, (*result)[:length])
19+
length *= rate
20+
*result = newResult
21+
22+
return length
23+
}
24+
25+
func writeIntFirstBuffer(value uint32, result *[]byte, offset int, length int) (int, int) {
26+
length = prepareBuffer(result, offset, length, 3)
27+
start := value >> 24
28+
if start == 0 {
29+
(*result)[offset] = byte(value >> 16)
30+
offset++
31+
(*result)[offset] = byte(value >> 8)
32+
offset++
33+
} else if start == 1 {
34+
(*result)[offset] = byte(value >> 8)
35+
offset++
36+
}
37+
(*result)[offset] = byte(value)
38+
offset++
39+
return offset, length
40+
}
41+
42+
func writeIntBuffer(value uint32, result *[]byte, offset int, length int) (int, int) {
43+
length = prepareBuffer(result, offset, length, 3)
44+
(*result)[offset] = byte(value >> 16)
45+
offset++
46+
(*result)[offset] = byte(value >> 8)
47+
offset++
48+
(*result)[offset] = byte(value)
49+
offset++
50+
return offset, length
51+
}
52+
53+
func writeInt(value int64, result *[]byte, offset int, length int) (int, int) {
54+
if value < 0 {
55+
value = -value
56+
length = prepareBuffer(result, offset, length, 1)
57+
(*result)[offset] = '-'
58+
offset++
59+
}
60+
q1 := value / 1000
61+
if q1 == 0 {
62+
return writeIntFirstBuffer(digits[value], result, offset, length)
63+
}
64+
r1 := value - q1*1000
65+
q2 := q1 / 1000
66+
if q2 == 0 {
67+
offset, length = writeIntFirstBuffer(digits[q1], result, offset, length)
68+
return writeIntBuffer(digits[r1], result, offset, length)
69+
}
70+
r2 := q1 - q2*1000
71+
q3 := q2 / 1000
72+
if q3 == 0 {
73+
offset, length = writeIntFirstBuffer(digits[q2], result, offset, length)
74+
offset, length = writeIntBuffer(digits[r2], result, offset, length)
75+
return writeIntBuffer(digits[r1], result, offset, length)
76+
}
77+
r3 := q2 - q3*1000
78+
q4 := q3 / 1000
79+
if q4 == 0 {
80+
offset, length = writeIntFirstBuffer(digits[q3], result, offset, length)
81+
offset, length = writeIntBuffer(digits[r3], result, offset, length)
82+
offset, length = writeIntBuffer(digits[r2], result, offset, length)
83+
return writeIntBuffer(digits[r1], result, offset, length)
84+
}
85+
r4 := q3 - q4*1000
86+
q5 := q4 / 1000
87+
if q5 == 0 {
88+
offset, length = writeIntFirstBuffer(digits[q4], result, offset, length)
89+
offset, length = writeIntBuffer(digits[r4], result, offset, length)
90+
offset, length = writeIntBuffer(digits[r3], result, offset, length)
91+
offset, length = writeIntBuffer(digits[r2], result, offset, length)
92+
return writeIntBuffer(digits[r1], result, offset, length)
93+
}
94+
r5 := q4 - q5*1000
95+
q6 := q5 / 1000
96+
if q6 == 0 {
97+
offset, length = writeIntFirstBuffer(digits[q5], result, offset, length)
98+
} else {
99+
offset, length = writeIntFirstBuffer(digits[q6], result, offset, length)
100+
r6 := q5 - q6*1000
101+
offset, length = writeIntBuffer(digits[r6], result, offset, length)
102+
}
103+
offset, length = writeIntBuffer(digits[r5], result, offset, length)
104+
offset, length = writeIntBuffer(digits[r4], result, offset, length)
105+
offset, length = writeIntBuffer(digits[r3], result, offset, length)
106+
offset, length = writeIntBuffer(digits[r2], result, offset, length)
107+
return writeIntBuffer(digits[r1], result, offset, length)
108+
}
8109

9110
func Marshal(data interface{}) ([]byte, error) {
10111
return MarshalTo(make([]byte, 512), data)
@@ -84,56 +185,25 @@ func marshal(data interface{}, result *[]byte, offset int, length int) (int, int
84185
}
85186
}
86187

87-
func prepareBuffer(result *[]byte, offset int, length int, neededLength int) int {
88-
availableLength := length - offset
89-
if availableLength >= neededLength {
90-
return length
91-
}
92-
93-
rate := 1
94-
for availableLength < neededLength {
95-
rate++
96-
availableLength = length*rate - offset
97-
}
98-
99-
if rate > 1 {
100-
newResult := make([]byte, length*rate)
101-
copy(newResult, (*result)[:length])
102-
length *= rate
103-
*result = newResult
104-
}
105-
106-
return length
107-
}
108-
109188
func marshalInt(data int64, result *[]byte, offset int, length int) (int, int) {
110-
intBuffer := s2b(strconv.FormatInt(data, 10))
111-
intBufferLength := len(intBuffer)
112-
length = prepareBuffer(result, offset, length, intBufferLength+2)
113-
189+
length = prepareBuffer(result, offset, length, 1)
114190
(*result)[offset] = 'i'
115191
offset++
116-
copy((*result)[offset:], intBuffer)
117-
offset += intBufferLength
192+
offset, length = writeInt(data, result, offset, length)
193+
length = prepareBuffer(result, offset, length, 1)
118194
(*result)[offset] = 'e'
119195
offset++
120-
121196
return offset, length
122197
}
123198

124199
func marshalBytes(data []byte, result *[]byte, offset int, length int) (int, int) {
125200
dataLength := len(data)
126-
lengthBuffer := s2b(strconv.Itoa(dataLength))
127-
lengthBufferLength := len(lengthBuffer)
128-
length = prepareBuffer(result, offset, length, lengthBufferLength+1+dataLength)
129-
130-
copy((*result)[offset:], lengthBuffer)
131-
offset += lengthBufferLength
201+
offset, length = writeInt(int64(dataLength), result, offset, length)
202+
length = prepareBuffer(result, offset, length, dataLength+1)
132203
(*result)[offset] = ':'
133204
offset++
134205
copy((*result)[offset:], data)
135206
offset += dataLength
136-
137207
return offset, length
138208
}
139209

@@ -169,7 +239,7 @@ func marshalDictionary(data map[string]interface{}, result *[]byte, offset int,
169239
for key, _ := range data {
170240
keys = append(keys, key)
171241
}
172-
sort.Strings(keys)
242+
sortStrings(keys)
173243

174244
for _, key := range keys {
175245
offset, length = marshalBytes(s2b(key), result, offset, length)

marshaler_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,16 @@ func TestMarshal(t *testing.T) {
5252
return
5353
}
5454

55+
result, err = Marshal(marshalTestData)
56+
if !assert.NoError(err) || !assert.Equal(unmarshalTestData, result) {
57+
return
58+
}
59+
60+
result, err = MarshalTo(make([]byte, 1), marshalTestData)
61+
if !assert.NoError(err) || !assert.Equal(unmarshalTestData, result) {
62+
return
63+
}
64+
5565
result, err = Marshal(nil)
5666
if !assert.Error(err) || !assert.Nil(result) || !assert.Equal("bencode: unsupported type: <nil>", err.Error()) {
5767
return

util.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,41 @@ package bencode
22

33
import (
44
"reflect"
5+
"sort"
56
"unsafe"
67
)
78

9+
var digits []uint32
10+
11+
func init() {
12+
digits = make([]uint32, 1000)
13+
for i := uint32(0); i < 1000; i++ {
14+
digits[i] = (((i / 100) + '0') << 16) + ((((i / 10) % 10) + '0') << 8) + i%10 + '0'
15+
if i < 10 {
16+
digits[i] += 2 << 24
17+
} else if i < 100 {
18+
digits[i] += 1 << 24
19+
}
20+
}
21+
}
22+
23+
const strSliceLen = 20
24+
25+
func sortStrings(ss []string) {
26+
if len(ss) <= strSliceLen {
27+
for i := 1; i < len(ss); i++ {
28+
for j := i; j > 0; j-- {
29+
if ss[j] >= ss[j-1] {
30+
break
31+
}
32+
ss[j], ss[j-1] = ss[j-1], ss[j]
33+
}
34+
}
35+
} else {
36+
sort.Strings(ss)
37+
}
38+
}
39+
840
// https://github.com/valyala/fastjson/blob/master/util.go
941

1042
func b2s(b []byte) string {

0 commit comments

Comments
 (0)