Skip to content

Commit 230fb69

Browse files
committed
wire: Optimize writes to bytes.Buffer
This special cases writes to bytes.Buffer, which is always the writer type written to by WriteMessageN. There are several optimizations that can be implemented by special casing this type: First, pulling temporary short buffers from binary freelist can be skipped entirely, and instead the binary encoding of integers can be appended directly to its existing capacity. This avoids the synchronization cost to add and remove buffers from the free list, and for applications which only ever write wire messages with WriteMessageN, the allocation and ongoing garbage collection scanning cost to for these buffers can be completely skipped. Second, special casing the buffer type in WriteVarString saves us from creating a temporary heap copy of a string, as the buffer's WriteString method can be used instead. Third, special casing the buffer allows MsgBlock and MsgTx BtcEncode method to calculate the serialize size and grow the buffer being written to, so all remaining appends will not have to reallocate the buffer's backing allocation. This same optimization can be applied to other messages in the future.
1 parent 45726d5 commit 230fb69

File tree

9 files changed

+159
-92
lines changed

9 files changed

+159
-92
lines changed

wire/common.go

Lines changed: 91 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package wire
77

88
import (
9+
"bytes"
910
"crypto/rand"
1011
"encoding/binary"
1112
"fmt"
@@ -131,49 +132,6 @@ func (l binaryFreeList) Uint64(r io.Reader, byteOrder binary.ByteOrder) (uint64,
131132
return rv, nil
132133
}
133134

134-
// PutUint8 copies the provided uint8 into a buffer from the free list and
135-
// writes the resulting byte to the given writer.
136-
func (l binaryFreeList) PutUint8(w io.Writer, val uint8) error {
137-
buf := l.Borrow()[:1]
138-
buf[0] = val
139-
_, err := w.Write(buf)
140-
l.Return(buf)
141-
return err
142-
}
143-
144-
// PutUint16 serializes the provided uint16 using the given byte order into a
145-
// buffer from the free list and writes the resulting two bytes to the given
146-
// writer.
147-
func (l binaryFreeList) PutUint16(w io.Writer, byteOrder binary.ByteOrder, val uint16) error {
148-
buf := l.Borrow()[:2]
149-
byteOrder.PutUint16(buf, val)
150-
_, err := w.Write(buf)
151-
l.Return(buf)
152-
return err
153-
}
154-
155-
// PutUint32 serializes the provided uint32 using the given byte order into a
156-
// buffer from the free list and writes the resulting four bytes to the given
157-
// writer.
158-
func (l binaryFreeList) PutUint32(w io.Writer, byteOrder binary.ByteOrder, val uint32) error {
159-
buf := l.Borrow()[:4]
160-
byteOrder.PutUint32(buf, val)
161-
_, err := w.Write(buf)
162-
l.Return(buf)
163-
return err
164-
}
165-
166-
// PutUint64 serializes the provided uint64 using the given byte order into a
167-
// buffer from the free list and writes the resulting eight bytes to the given
168-
// writer.
169-
func (l binaryFreeList) PutUint64(w io.Writer, byteOrder binary.ByteOrder, val uint64) error {
170-
buf := l.Borrow()[:8]
171-
byteOrder.PutUint64(buf, val)
172-
_, err := w.Write(buf)
173-
l.Return(buf)
174-
return err
175-
}
176-
177135
// binarySerializer provides a free list of buffers to use for serializing and
178136
// deserializing primitive integer values to and from io.Readers and io.Writers.
179137
var binarySerializer binaryFreeList = make(chan []byte, binaryFreeListMaxItems)
@@ -412,60 +370,100 @@ func readElements(r io.Reader, elements ...interface{}) error {
412370
return nil
413371
}
414372

373+
// The most common case (called through WriteMessageN) is that the writer is a
374+
// *bytes.Buffer. Optimize for that case by appending binary serializations
375+
// to its existing capacity instead of paying the synchronization cost to
376+
// serialize to temporary buffers pulled from the binary freelist. This is
377+
// wrapped by this helper function with callback as only binary encoding short
378+
// writes (<= 8 bytes) requires the append (in all other cases, Write can be
379+
// called directly).
380+
func appendWriter(w io.Writer, do func(p []byte) []byte) (rerr error) {
381+
var p []byte
382+
switch w := w.(type) {
383+
case *bytes.Buffer:
384+
p = w.Bytes()[w.Len():]
385+
defer func() {
386+
w.Write(p)
387+
}()
388+
default:
389+
p = binarySerializer.Borrow()[:0]
390+
defer func() {
391+
_, rerr = w.Write(p)
392+
binarySerializer.Return(p)
393+
}()
394+
}
395+
396+
p = do(p)
397+
return nil
398+
}
399+
415400
// writeElement writes the little endian representation of element to w.
416401
func writeElement(w io.Writer, element interface{}) error {
417402
// Attempt to write the element based on the concrete type via fast
418403
// type assertions first.
419404
switch e := element.(type) {
420405
case *uint8:
421-
err := binarySerializer.PutUint8(w, *e)
406+
err := appendWriter(w, func(p []byte) []byte {
407+
return append(p, *e)
408+
})
422409
if err != nil {
423410
return err
424411
}
425412
return nil
426413

427414
case *uint16:
428-
err := binarySerializer.PutUint16(w, littleEndian, *e)
415+
err := appendWriter(w, func(p []byte) []byte {
416+
return littleEndian.AppendUint16(p, *e)
417+
})
429418
if err != nil {
430419
return err
431420
}
432421
return nil
433422

434423
case *int32:
435-
err := binarySerializer.PutUint32(w, littleEndian, uint32(*e))
424+
err := appendWriter(w, func(p []byte) []byte {
425+
return littleEndian.AppendUint32(p, uint32(*e))
426+
})
436427
if err != nil {
437428
return err
438429
}
439430
return nil
440431

441432
case *uint32:
442-
err := binarySerializer.PutUint32(w, littleEndian, *e)
433+
err := appendWriter(w, func(p []byte) []byte {
434+
return littleEndian.AppendUint32(p, *e)
435+
})
443436
if err != nil {
444437
return err
445438
}
446439
return nil
447440

448441
case *int64:
449-
err := binarySerializer.PutUint64(w, littleEndian, uint64(*e))
442+
err := appendWriter(w, func(p []byte) []byte {
443+
return littleEndian.AppendUint64(p, uint64(*e))
444+
})
450445
if err != nil {
451446
return err
452447
}
453448
return nil
454449

455450
case *uint64:
456-
err := binarySerializer.PutUint64(w, littleEndian, *e)
451+
err := appendWriter(w, func(p []byte) []byte {
452+
return littleEndian.AppendUint64(p, *e)
453+
})
457454
if err != nil {
458455
return err
459456
}
460457
return nil
461458

462459
case *bool:
463-
var err error
464-
if *e {
465-
err = binarySerializer.PutUint8(w, 0x01)
466-
} else {
467-
err = binarySerializer.PutUint8(w, 0x00)
468-
}
460+
err := appendWriter(w, func(p []byte) []byte {
461+
var value byte
462+
if *e {
463+
value = 1
464+
}
465+
return append(p, value)
466+
})
469467
if err != nil {
470468
return err
471469
}
@@ -558,28 +556,36 @@ func writeElement(w io.Writer, element interface{}) error {
558556
return nil
559557

560558
case *ServiceFlag:
561-
err := binarySerializer.PutUint64(w, littleEndian, uint64(*e))
559+
err := appendWriter(w, func(p []byte) []byte {
560+
return littleEndian.AppendUint64(p, uint64(*e))
561+
})
562562
if err != nil {
563563
return err
564564
}
565565
return nil
566566

567567
case *InvType:
568-
err := binarySerializer.PutUint32(w, littleEndian, uint32(*e))
568+
err := appendWriter(w, func(p []byte) []byte {
569+
return littleEndian.AppendUint32(p, uint32(*e))
570+
})
569571
if err != nil {
570572
return err
571573
}
572574
return nil
573575

574576
case *CurrencyNet:
575-
err := binarySerializer.PutUint32(w, littleEndian, uint32(*e))
577+
err := appendWriter(w, func(p []byte) []byte {
578+
return littleEndian.AppendUint32(p, uint32(*e))
579+
})
576580
if err != nil {
577581
return err
578582
}
579583
return nil
580584

581585
case *RejectCode:
582-
err := binarySerializer.PutUint8(w, uint8(*e))
586+
err := appendWriter(w, func(p []byte) []byte {
587+
return append(p, uint8(*e))
588+
})
583589
if err != nil {
584590
return err
585591
}
@@ -669,30 +675,36 @@ func ReadVarInt(r io.Reader, pver uint32) (uint64, error) {
669675
// on its value.
670676
func WriteVarInt(w io.Writer, pver uint32, val uint64) error {
671677
if val < 0xfd {
672-
return binarySerializer.PutUint8(w, uint8(val))
678+
return appendWriter(w, func(p []byte) []byte {
679+
return append(p, uint8(val))
680+
})
673681
}
674682

675683
if val <= math.MaxUint16 {
676-
err := binarySerializer.PutUint8(w, 0xfd)
677-
if err != nil {
678-
return err
679-
}
680-
return binarySerializer.PutUint16(w, littleEndian, uint16(val))
684+
return appendWriter(w, func(p []byte) []byte {
685+
p = append(p, 0xfd)
686+
p = littleEndian.AppendUint16(p, uint16(val))
687+
return p
688+
})
681689
}
682690

683691
if val <= math.MaxUint32 {
684-
err := binarySerializer.PutUint8(w, 0xfe)
685-
if err != nil {
686-
return err
687-
}
688-
return binarySerializer.PutUint32(w, littleEndian, uint32(val))
692+
return appendWriter(w, func(p []byte) []byte {
693+
p = append(p, 0xfe)
694+
p = littleEndian.AppendUint32(p, uint32(val))
695+
return p
696+
})
689697
}
690698

691-
err := binarySerializer.PutUint8(w, 0xff)
699+
err := appendWriter(w, func(p []byte) []byte {
700+
return append(p, 0xff)
701+
})
692702
if err != nil {
693703
return err
694704
}
695-
return binarySerializer.PutUint64(w, littleEndian, val)
705+
return appendWriter(w, func(p []byte) []byte {
706+
return littleEndian.AppendUint64(p, val)
707+
})
696708
}
697709

698710
// VarIntSerializeSize returns the number of bytes it would take to serialize
@@ -798,7 +810,13 @@ func WriteVarString(w io.Writer, pver uint32, str string) error {
798810
if err != nil {
799811
return err
800812
}
801-
_, err = w.Write([]byte(str))
813+
814+
switch w := w.(type) {
815+
case *bytes.Buffer:
816+
_, err = w.WriteString(str)
817+
default:
818+
_, err = w.Write([]byte(str))
819+
}
802820
return err
803821
}
804822

wire/msgblock.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,11 @@ func (msg *MsgBlock) DeserializeTxLoc(r *bytes.Buffer) ([]TxLoc, []TxLoc, error)
252252
// See Serialize for encoding blocks to be stored to disk, such as in a
253253
// database, as opposed to encoding blocks for the wire.
254254
func (msg *MsgBlock) BtcEncode(w io.Writer, pver uint32) error {
255+
switch w := w.(type) {
256+
case *bytes.Buffer:
257+
w.Grow(msg.SerializeSize())
258+
}
259+
255260
err := writeBlockHeader(w, pver, &msg.Header)
256261
if err != nil {
257262
return err
@@ -263,7 +268,7 @@ func (msg *MsgBlock) BtcEncode(w io.Writer, pver uint32) error {
263268
}
264269

265270
for _, tx := range msg.Transactions {
266-
err = tx.BtcEncode(w, pver)
271+
err = tx.btcEncode(w, pver)
267272
if err != nil {
268273
return err
269274
}
@@ -275,7 +280,7 @@ func (msg *MsgBlock) BtcEncode(w io.Writer, pver uint32) error {
275280
}
276281

277282
for _, tx := range msg.STransactions {
278-
err = tx.BtcEncode(w, pver)
283+
err = tx.btcEncode(w, pver)
279284
if err != nil {
280285
return err
281286
}

wire/msgcfheaders.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,9 @@ func (msg *MsgCFHeaders) BtcEncode(w io.Writer, pver uint32) error {
116116
}
117117

118118
// Write filter type
119-
err = binarySerializer.PutUint8(w, uint8(msg.FilterType))
119+
err = appendWriter(w, func(p []byte) []byte {
120+
return append(p, uint8(msg.FilterType))
121+
})
120122
if err != nil {
121123
return err
122124
}

wire/msgcfilter.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,9 @@ func (msg *MsgCFilter) BtcEncode(w io.Writer, pver uint32) error {
8080
return err
8181
}
8282

83-
err = binarySerializer.PutUint8(w, uint8(msg.FilterType))
83+
err = appendWriter(w, func(p []byte) []byte {
84+
return append(p, uint8(msg.FilterType))
85+
})
8486
if err != nil {
8587
return err
8688
}

wire/msgcftypes.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,9 @@ func (msg *MsgCFTypes) BtcEncode(w io.Writer, pver uint32) error {
9292
}
9393

9494
for i := range msg.SupportedFilters {
95-
err = binarySerializer.PutUint8(w, uint8(msg.SupportedFilters[i]))
95+
err = appendWriter(w, func(p []byte) []byte {
96+
return append(p, uint8(msg.SupportedFilters[i]))
97+
})
9698
if err != nil {
9799
return err
98100
}

wire/msggetcfheaders.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,9 @@ func (msg *MsgGetCFHeaders) BtcEncode(w io.Writer, pver uint32) error {
115115
return err
116116
}
117117

118-
return binarySerializer.PutUint8(w, uint8(msg.FilterType))
118+
return appendWriter(w, func(p []byte) []byte {
119+
return append(p, uint8(msg.FilterType))
120+
})
119121
}
120122

121123
// Command returns the protocol command string for the message. This is part

wire/msggetcfilter.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,9 @@ func (msg *MsgGetCFilter) BtcEncode(w io.Writer, pver uint32) error {
5454
if err != nil {
5555
return err
5656
}
57-
return binarySerializer.PutUint8(w, uint8(msg.FilterType))
57+
return appendWriter(w, func(p []byte) []byte {
58+
return append(p, uint8(msg.FilterType))
59+
})
5860
}
5961

6062
// Command returns the protocol command string for the message. This is part

wire/msgmixconfirm.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ func (msg *MsgMixConfirm) writeMessageNoSignature(op string, w io.Writer, pver u
145145
return err
146146
}
147147

148+
// XXX: use btcEncode after providing our own initial size hint.
148149
err = msg.Mix.BtcEncode(w, pver)
149150
if err != nil {
150151
return err

0 commit comments

Comments
 (0)