Skip to content

Commit 2e46fd2

Browse files
committed
p2p, rlp: Optimize rlp encode/decode
1 parent 18cc7b0 commit 2e46fd2

File tree

14 files changed

+951
-437
lines changed

14 files changed

+951
-437
lines changed

p2p/rlpx/buffer.go

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
// Copyright 2021 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package rlpx
18+
19+
import (
20+
"io"
21+
)
22+
23+
// readBuffer implements buffering for network reads. This type is similar to bufio.Reader,
24+
// with two crucial differences: the buffer slice is exposed, and the buffer keeps all
25+
// read data available until reset.
26+
//
27+
// How to use this type:
28+
//
29+
// Keep a readBuffer b alongside the underlying network connection. When reading a packet
30+
// from the connection, first call b.reset(). This empties b.data. Now perform reads
31+
// through b.read() until the end of the packet is reached. The complete packet data is
32+
// now available in b.data.
33+
type readBuffer struct {
34+
data []byte
35+
end int
36+
}
37+
38+
// reset removes all processed data which was read since the last call to reset.
39+
// After reset, len(b.data) is zero.
40+
func (b *readBuffer) reset() {
41+
unprocessed := b.end - len(b.data)
42+
copy(b.data[:unprocessed], b.data[len(b.data):b.end])
43+
b.end = unprocessed
44+
b.data = b.data[:0]
45+
}
46+
47+
// read reads at least n bytes from r, returning the bytes.
48+
// The returned slice is valid until the next call to reset.
49+
func (b *readBuffer) read(r io.Reader, n int) ([]byte, error) {
50+
offset := len(b.data)
51+
have := b.end - len(b.data)
52+
53+
// If n bytes are available in the buffer, there is no need to read from r at all.
54+
if have >= n {
55+
b.data = b.data[:offset+n]
56+
return b.data[offset : offset+n], nil
57+
}
58+
59+
// Make buffer space available.
60+
need := n - have
61+
b.grow(need)
62+
63+
// Read.
64+
rn, err := io.ReadAtLeast(r, b.data[b.end:cap(b.data)], need)
65+
if err != nil {
66+
return nil, err
67+
}
68+
b.end += rn
69+
b.data = b.data[:offset+n]
70+
return b.data[offset : offset+n], nil
71+
}
72+
73+
// grow ensures the buffer has at least n bytes of unused space.
74+
func (b *readBuffer) grow(n int) {
75+
if cap(b.data)-b.end >= n {
76+
return
77+
}
78+
need := n - (cap(b.data) - b.end)
79+
offset := len(b.data)
80+
b.data = append(b.data[:cap(b.data)], make([]byte, need)...)
81+
b.data = b.data[:offset]
82+
}
83+
84+
// writeBuffer implements buffering for network writes. This is essentially
85+
// a convenience wrapper around a byte slice.
86+
type writeBuffer struct {
87+
data []byte
88+
}
89+
90+
func (b *writeBuffer) reset() {
91+
b.data = b.data[:0]
92+
}
93+
94+
func (b *writeBuffer) appendZero(n int) []byte {
95+
offset := len(b.data)
96+
b.data = append(b.data, make([]byte, n)...)
97+
return b.data[offset : offset+n]
98+
}
99+
100+
func (b *writeBuffer) Write(data []byte) (int, error) {
101+
b.data = append(b.data, data...)
102+
return len(data), nil
103+
}
104+
105+
const maxUint24 = int(^uint32(0) >> 8)
106+
107+
func readUint24(b []byte) uint32 {
108+
return uint32(b[2]) | uint32(b[1])<<8 | uint32(b[0])<<16
109+
}
110+
111+
func putUint24(v uint32, b []byte) {
112+
b[0] = byte(v >> 16)
113+
b[1] = byte(v >> 8)
114+
b[2] = byte(v)
115+
}
116+
117+
// growslice ensures b has the wanted length by either expanding it to its capacity
118+
// or allocating a new slice if b has insufficient capacity.
119+
func growslice(b []byte, wantLength int) []byte {
120+
if len(b) >= wantLength {
121+
return b
122+
}
123+
if cap(b) >= wantLength {
124+
return b[:cap(b)]
125+
}
126+
return make([]byte, wantLength)
127+
}

p2p/rlpx/buffer_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright 2021 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package rlpx
18+
19+
import (
20+
"bytes"
21+
"testing"
22+
23+
"github.com/ethereum/go-ethereum/common/hexutil"
24+
"github.com/stretchr/testify/assert"
25+
)
26+
27+
func TestReadBufferReset(t *testing.T) {
28+
reader := bytes.NewReader(hexutil.MustDecode("0x010202030303040505"))
29+
var b readBuffer
30+
31+
s1, _ := b.read(reader, 1)
32+
s2, _ := b.read(reader, 2)
33+
s3, _ := b.read(reader, 3)
34+
35+
assert.Equal(t, []byte{1}, s1)
36+
assert.Equal(t, []byte{2, 2}, s2)
37+
assert.Equal(t, []byte{3, 3, 3}, s3)
38+
39+
b.reset()
40+
41+
s4, _ := b.read(reader, 1)
42+
s5, _ := b.read(reader, 2)
43+
44+
assert.Equal(t, []byte{4}, s4)
45+
assert.Equal(t, []byte{5, 5}, s5)
46+
47+
s6, err := b.read(reader, 2)
48+
49+
assert.EqualError(t, err, "EOF")
50+
assert.Nil(t, s6)
51+
}

0 commit comments

Comments
 (0)