Skip to content

Commit 84a65f2

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 two 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.
1 parent 45726d5 commit 84a65f2

File tree

1 file changed

+91
-30
lines changed

1 file changed

+91
-30
lines changed

wire/common.go

Lines changed: 91 additions & 30 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"
@@ -412,60 +413,100 @@ func readElements(r io.Reader, elements ...interface{}) error {
412413
return nil
413414
}
414415

416+
// The most common case (called through WriteMessageN) is that the writer is a
417+
// *bytes.Buffer. Optimize for that case by appending binary serializations
418+
// to its existing capacity instead of paying the synchronization cost to
419+
// serialize to temporary buffers pulled from the binary freelist. This is
420+
// wrapped by this helper function with callback as only binary encoding short
421+
// writes (<= 8 bytes) requires the append (in all other cases, Write can be
422+
// called directly).
423+
func appendWriter(w io.Writer, do func(p []byte) []byte) (rerr error) {
424+
var p []byte
425+
switch w := w.(type) {
426+
case *bytes.Buffer:
427+
p = w.Bytes()[w.Len():]
428+
defer func() {
429+
w.Write(p)
430+
}()
431+
default:
432+
p = binarySerializer.Borrow()[:0]
433+
defer func() {
434+
_, rerr = w.Write(p)
435+
binarySerializer.Return(p)
436+
}()
437+
}
438+
439+
p = do(p)
440+
return nil
441+
}
442+
415443
// writeElement writes the little endian representation of element to w.
416444
func writeElement(w io.Writer, element interface{}) error {
417445
// Attempt to write the element based on the concrete type via fast
418446
// type assertions first.
419447
switch e := element.(type) {
420448
case *uint8:
421-
err := binarySerializer.PutUint8(w, *e)
449+
err := appendWriter(w, func(p []byte) []byte {
450+
return append(p, *e)
451+
})
422452
if err != nil {
423453
return err
424454
}
425455
return nil
426456

427457
case *uint16:
428-
err := binarySerializer.PutUint16(w, littleEndian, *e)
458+
err := appendWriter(w, func(p []byte) []byte {
459+
return littleEndian.AppendUint16(p, *e)
460+
})
429461
if err != nil {
430462
return err
431463
}
432464
return nil
433465

434466
case *int32:
435-
err := binarySerializer.PutUint32(w, littleEndian, uint32(*e))
467+
err := appendWriter(w, func(p []byte) []byte {
468+
return littleEndian.AppendUint32(p, uint32(*e))
469+
})
436470
if err != nil {
437471
return err
438472
}
439473
return nil
440474

441475
case *uint32:
442-
err := binarySerializer.PutUint32(w, littleEndian, *e)
476+
err := appendWriter(w, func(p []byte) []byte {
477+
return littleEndian.AppendUint32(p, *e)
478+
})
443479
if err != nil {
444480
return err
445481
}
446482
return nil
447483

448484
case *int64:
449-
err := binarySerializer.PutUint64(w, littleEndian, uint64(*e))
485+
err := appendWriter(w, func(p []byte) []byte {
486+
return littleEndian.AppendUint64(p, uint64(*e))
487+
})
450488
if err != nil {
451489
return err
452490
}
453491
return nil
454492

455493
case *uint64:
456-
err := binarySerializer.PutUint64(w, littleEndian, *e)
494+
err := appendWriter(w, func(p []byte) []byte {
495+
return littleEndian.AppendUint64(p, *e)
496+
})
457497
if err != nil {
458498
return err
459499
}
460500
return nil
461501

462502
case *bool:
463-
var err error
464-
if *e {
465-
err = binarySerializer.PutUint8(w, 0x01)
466-
} else {
467-
err = binarySerializer.PutUint8(w, 0x00)
468-
}
503+
err := appendWriter(w, func(p []byte) []byte {
504+
var value byte
505+
if *e {
506+
value = 1
507+
}
508+
return append(p, value)
509+
})
469510
if err != nil {
470511
return err
471512
}
@@ -558,28 +599,36 @@ func writeElement(w io.Writer, element interface{}) error {
558599
return nil
559600

560601
case *ServiceFlag:
561-
err := binarySerializer.PutUint64(w, littleEndian, uint64(*e))
602+
err := appendWriter(w, func(p []byte) []byte {
603+
return littleEndian.AppendUint64(p, uint64(*e))
604+
})
562605
if err != nil {
563606
return err
564607
}
565608
return nil
566609

567610
case *InvType:
568-
err := binarySerializer.PutUint32(w, littleEndian, uint32(*e))
611+
err := appendWriter(w, func(p []byte) []byte {
612+
return littleEndian.AppendUint32(p, uint32(*e))
613+
})
569614
if err != nil {
570615
return err
571616
}
572617
return nil
573618

574619
case *CurrencyNet:
575-
err := binarySerializer.PutUint32(w, littleEndian, uint32(*e))
620+
err := appendWriter(w, func(p []byte) []byte {
621+
return littleEndian.AppendUint32(p, uint32(*e))
622+
})
576623
if err != nil {
577624
return err
578625
}
579626
return nil
580627

581628
case *RejectCode:
582-
err := binarySerializer.PutUint8(w, uint8(*e))
629+
err := appendWriter(w, func(p []byte) []byte {
630+
return append(p, uint8(*e))
631+
})
583632
if err != nil {
584633
return err
585634
}
@@ -669,30 +718,36 @@ func ReadVarInt(r io.Reader, pver uint32) (uint64, error) {
669718
// on its value.
670719
func WriteVarInt(w io.Writer, pver uint32, val uint64) error {
671720
if val < 0xfd {
672-
return binarySerializer.PutUint8(w, uint8(val))
721+
return appendWriter(w, func(p []byte) []byte {
722+
return append(p, uint8(val))
723+
})
673724
}
674725

675726
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))
727+
return appendWriter(w, func(p []byte) []byte {
728+
p = append(p, 0xfd)
729+
p = littleEndian.AppendUint16(p, uint16(val))
730+
return p
731+
})
681732
}
682733

683734
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))
735+
return appendWriter(w, func(p []byte) []byte {
736+
p = append(p, 0xfe)
737+
p = littleEndian.AppendUint32(p, uint32(val))
738+
return p
739+
})
689740
}
690741

691-
err := binarySerializer.PutUint8(w, 0xff)
742+
err := appendWriter(w, func(p []byte) []byte {
743+
return append(p, 0xff)
744+
})
692745
if err != nil {
693746
return err
694747
}
695-
return binarySerializer.PutUint64(w, littleEndian, val)
748+
return appendWriter(w, func(p []byte) []byte {
749+
return littleEndian.AppendUint64(p, val)
750+
})
696751
}
697752

698753
// VarIntSerializeSize returns the number of bytes it would take to serialize
@@ -798,7 +853,13 @@ func WriteVarString(w io.Writer, pver uint32, str string) error {
798853
if err != nil {
799854
return err
800855
}
801-
_, err = w.Write([]byte(str))
856+
857+
switch w := w.(type) {
858+
case *bytes.Buffer:
859+
_, err = w.WriteString(str)
860+
default:
861+
_, err = w.Write([]byte(str))
862+
}
802863
return err
803864
}
804865

0 commit comments

Comments
 (0)