Skip to content

Commit 26461ed

Browse files
committed
update encoder
1 parent a5d3eb5 commit 26461ed

File tree

11 files changed

+171
-166
lines changed

11 files changed

+171
-166
lines changed

README.md

+10-9
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,16 @@ data, err := bencode.Unmarshal(value)
4141

4242
### Marshal
4343

44-
| Library | Time | Bytes Allocated | Objects Allocated |
45-
| :------------------ | :---------: | :-------------: | :---------------: |
46-
| IncSW/go-bencode | 614.4 ns/op | 112 B/op | 2 allocs/op |
47-
| marksamman/bencode | 820.3 ns/op | 384 B/op | 8 allocs/op |
48-
| cristalhq/bencode | 994.2 ns/op | 928 B/op | 4 allocs/op |
49-
| aleksatr/go-bencode | 1061 ns/op | 736 B/op | 9 allocs/op |
50-
| nabilanam/bencode | 2103 ns/op | 1192 B/op | 44 allocs/op |
51-
| jackpal/bencode-go | 4676 ns/op | 2016 B/op | 45 allocs/op |
52-
| zeebo/bencode | 4889 ns/op | 1376 B/op | 33 allocs/op |
44+
| Library | Time | Bytes Allocated | Objects Allocated |
45+
| :--------------------------- | :---------: | :-------------: | :---------------: |
46+
| IncSW/go-bencode [MarshalTo] | 590.8 ns/op | 112 B/op | 2 allocs/op |
47+
| IncSW/go-bencode [Marshal] | 676.1 ns/op | 624 B/op | 3 allocs/op |
48+
| marksamman/bencode | 820.3 ns/op | 384 B/op | 8 allocs/op |
49+
| cristalhq/bencode | 994.2 ns/op | 928 B/op | 4 allocs/op |
50+
| aleksatr/go-bencode | 1061 ns/op | 736 B/op | 9 allocs/op |
51+
| nabilanam/bencode | 2103 ns/op | 1192 B/op | 44 allocs/op |
52+
| jackpal/bencode-go | 4676 ns/op | 2016 B/op | 45 allocs/op |
53+
| zeebo/bencode | 4889 ns/op | 1376 B/op | 33 allocs/op |
5354

5455
### Unmarshal
5556

bencode.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ import (
66
)
77

88
func MarshalTo(dst []byte, data interface{}) ([]byte, error) {
9-
return encoder.MarshalTo(dst, data)
9+
var e encoder.Encoder
10+
return e.EncodeTo(dst, data)
1011
}
1112

1213
func Marshal(data interface{}) ([]byte, error) {
13-
return encoder.MarshalTo(make([]byte, 512), data)
14+
var e encoder.Encoder
15+
return e.EncodeTo(nil, data)
1416
}
1517

1618
func Unmarshal(data []byte) (interface{}, error) {

encoder_test.go

+13-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package bencode
22

33
import (
4+
"runtime"
45
"testing"
56

67
"github.com/stretchr/testify/assert"
@@ -82,7 +83,16 @@ func TestMarshalUnOrderedDict(t *testing.T) {
8283

8384
func BenchmarkMarshal(b *testing.B) {
8485
b.ReportAllocs()
85-
for n := 0; n < b.N; n++ {
86-
Marshal(marshalTestData)
87-
}
86+
var result []byte
87+
b.Run("Marshal", func(b *testing.B) {
88+
for n := 0; n < b.N; n++ {
89+
result, _ = Marshal(marshalTestData)
90+
}
91+
})
92+
b.Run("MarshalTo", func(b *testing.B) {
93+
for n := 0; n < b.N; n++ {
94+
result, _ = MarshalTo(result, marshalTestData)
95+
}
96+
})
97+
runtime.KeepAlive(result)
8898
}

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ module github.com/IncSW/go-bencode
22

33
go 1.15
44

5-
require github.com/stretchr/testify v1.6.1
5+
require github.com/stretchr/testify v1.7.0

go.sum

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
33
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
44
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
55
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
6-
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
7-
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
6+
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
7+
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
88
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
99
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
1010
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
File renamed without changes.

internal/encoder/bytes.go

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package encoder
2+
3+
func (e *Encoder) encodeBytes(data []byte) {
4+
dataLength := len(data)
5+
e.grow(dataLength + 23)
6+
e.writeInt(int64(len(data)))
7+
e.writeByte(':')
8+
e.write(data)
9+
}

internal/encoder/dictionary.go

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package encoder
2+
3+
import "github.com/IncSW/go-bencode/internal"
4+
5+
func (e *Encoder) encodeDictionary(data map[string]interface{}) error {
6+
e.grow(1)
7+
e.writeByte('d')
8+
keys := make([]string, 0, len(data))
9+
for key, _ := range data {
10+
keys = append(keys, key)
11+
}
12+
internal.SortStrings(keys)
13+
for _, key := range keys {
14+
e.encodeBytes(internal.S2B(key))
15+
err := e.encode(data[key])
16+
if err != nil {
17+
return err
18+
}
19+
}
20+
e.grow(1)
21+
e.writeByte('e')
22+
return nil
23+
}

internal/encoder/encoder.go

+76-120
Original file line numberDiff line numberDiff line change
@@ -2,162 +2,118 @@ package encoder
22

33
import (
44
"fmt"
5+
"unsafe"
56

67
"github.com/IncSW/go-bencode/internal"
78
)
89

9-
func prepareBuffer(result *[]byte, offset int, length int, neededLength int) int {
10-
availableLength := length - offset
10+
//go:linkname memmove runtime.memmove
11+
//go:noescape
12+
func memmove(to unsafe.Pointer, from unsafe.Pointer, n uintptr)
13+
14+
type sliceHeader struct {
15+
data unsafe.Pointer
16+
len int
17+
cap int
18+
}
19+
20+
type Encoder struct {
21+
buffer []byte
22+
length int
23+
offset int
24+
}
25+
26+
//go:nosplit
27+
func (e *Encoder) grow(neededLength int) {
28+
availableLength := e.length - e.offset
1129
if availableLength >= neededLength {
12-
return length
30+
return
1331
}
14-
15-
rate := 1
16-
for availableLength < neededLength {
17-
rate++
18-
availableLength = length*rate - offset
32+
if e.length == 0 {
33+
if neededLength < 16 {
34+
neededLength = 16
35+
}
36+
e.length = neededLength
37+
availableLength = neededLength
38+
} else {
39+
for availableLength < neededLength {
40+
e.length += e.length
41+
availableLength = e.length - e.offset
42+
}
1943
}
44+
buffer := make([]byte, e.length)
45+
memmove(
46+
unsafe.Pointer(uintptr((*sliceHeader)(unsafe.Pointer(&buffer)).data)),
47+
(*sliceHeader)(unsafe.Pointer(&e.buffer)).data,
48+
uintptr(e.offset),
49+
)
50+
e.buffer = buffer
51+
}
2052

21-
newResult := make([]byte, length*rate)
22-
copy(newResult, (*result)[:length])
23-
length *= rate
24-
*result = newResult
53+
//go:nosplit
54+
func (e *Encoder) write(data []byte) {
55+
length := len(data)
56+
memmove(
57+
unsafe.Pointer(uintptr((*sliceHeader)(unsafe.Pointer(&e.buffer)).data)+uintptr(e.offset)),
58+
(*sliceHeader)(unsafe.Pointer(&data)).data,
59+
uintptr(length),
60+
)
61+
e.offset += length
62+
}
2563

26-
return length
64+
//go:nosplit
65+
func (e *Encoder) writeByte(data byte) {
66+
*(*byte)(unsafe.Pointer(uintptr((*sliceHeader)(unsafe.Pointer(&e.buffer)).data) + uintptr(e.offset))) = data
67+
e.offset++
2768
}
2869

29-
func MarshalTo(dst []byte, data interface{}) ([]byte, error) {
70+
func (e *Encoder) EncodeTo(dst []byte, data interface{}) ([]byte, error) {
3071
if cap(dst) > len(dst) {
3172
dst = dst[:cap(dst)]
3273
} else if len(dst) == 0 {
3374
dst = make([]byte, 512)
3475
}
35-
length, _, err := marshal(data, &dst, 0, len(dst))
76+
e.buffer = dst
77+
e.length = cap(dst)
78+
err := e.encode(data)
3679
if err != nil {
3780
return nil, err
3881
}
39-
return dst[:length], nil
82+
return e.buffer[:e.offset], nil
4083
}
4184

42-
func marshal(data interface{}, result *[]byte, offset int, length int) (int, int, error) {
85+
func (e *Encoder) encode(data interface{}) error {
4386
switch value := data.(type) {
4487
case int64:
45-
offset, length = marshalInt(value, result, offset, length)
46-
return offset, length, nil
47-
88+
e.encodeInt(value)
4889
case int32:
49-
offset, length = marshalInt(int64(value), result, offset, length)
50-
return offset, length, nil
51-
90+
e.encodeInt(int64(value))
5291
case int16:
53-
offset, length = marshalInt(int64(value), result, offset, length)
54-
return offset, length, nil
55-
92+
e.encodeInt(int64(value))
5693
case int8:
57-
offset, length = marshalInt(int64(value), result, offset, length)
58-
return offset, length, nil
59-
94+
e.encodeInt(int64(value))
6095
case int:
61-
offset, length = marshalInt(int64(value), result, offset, length)
62-
return offset, length, nil
63-
96+
e.encodeInt(int64(value))
6497
case uint64:
65-
offset, length = marshalInt(int64(value), result, offset, length)
66-
return offset, length, nil
67-
98+
e.encodeInt(int64(value))
6899
case uint32:
69-
offset, length = marshalInt(int64(value), result, offset, length)
70-
return offset, length, nil
71-
100+
e.encodeInt(int64(value))
72101
case uint16:
73-
offset, length = marshalInt(int64(value), result, offset, length)
74-
return offset, length, nil
75-
102+
e.encodeInt(int64(value))
76103
case uint8:
77-
offset, length = marshalInt(int64(value), result, offset, length)
78-
return offset, length, nil
79-
104+
e.encodeInt(int64(value))
80105
case uint:
81-
offset, length = marshalInt(int64(value), result, offset, length)
82-
return offset, length, nil
83-
106+
e.encodeInt(int64(value))
84107
case []byte:
85-
offset, length = marshalBytes(value, result, offset, length)
86-
return offset, length, nil
87-
108+
e.encodeBytes(value)
88109
case string:
89-
offset, length = marshalBytes(internal.S2B(value), result, offset, length)
90-
return offset, length, nil
91-
110+
e.encodeBytes(internal.S2B(value))
92111
case []interface{}:
93-
return marshalList(value, result, offset, length)
94-
112+
return e.encodeList(value)
95113
case map[string]interface{}:
96-
return marshalDictionary(value, result, offset, length)
97-
114+
return e.encodeDictionary(value)
98115
default:
99-
return 0, 0, fmt.Errorf("bencode: unsupported type: %T", value)
100-
}
101-
}
102-
103-
func marshalBytes(data []byte, result *[]byte, offset int, length int) (int, int) {
104-
dataLength := len(data)
105-
offset, length = writeInt(int64(dataLength), result, offset, length)
106-
length = prepareBuffer(result, offset, length, dataLength+1)
107-
(*result)[offset] = ':'
108-
offset++
109-
copy((*result)[offset:], data)
110-
offset += dataLength
111-
return offset, length
112-
}
113-
114-
func marshalList(data []interface{}, result *[]byte, offset int, length int) (int, int, error) {
115-
length = prepareBuffer(result, offset, length, 1)
116-
117-
(*result)[offset] = 'l'
118-
offset++
119-
120-
for _, data := range data {
121-
var err error
122-
offset, length, err = marshal(data, result, offset, length)
123-
if err != nil {
124-
return 0, 0, err
125-
}
116+
return fmt.Errorf("bencode: unsupported type: %T", value)
126117
}
127-
128-
length = prepareBuffer(result, offset, length, 1)
129-
130-
(*result)[offset] = 'e'
131-
offset++
132-
133-
return offset, length, nil
134-
}
135-
136-
func marshalDictionary(data map[string]interface{}, result *[]byte, offset int, length int) (int, int, error) {
137-
length = prepareBuffer(result, offset, length, 1)
138-
139-
(*result)[offset] = 'd'
140-
offset++
141-
142-
keys := make([]string, 0, len(data))
143-
for key, _ := range data {
144-
keys = append(keys, key)
145-
}
146-
internal.SortStrings(keys)
147-
148-
for _, key := range keys {
149-
offset, length = marshalBytes(internal.S2B(key), result, offset, length)
150-
var err error
151-
offset, length, err = marshal(data[key], result, offset, length)
152-
if err != nil {
153-
return 0, 0, err
154-
}
155-
}
156-
157-
length = prepareBuffer(result, offset, length, 1)
158-
159-
(*result)[offset] = 'e'
160-
offset++
161-
162-
return offset, length, nil
118+
return nil
163119
}

0 commit comments

Comments
 (0)