@@ -58,8 +58,10 @@ type Stream struct {
58
58
outdone chan struct {} // closed when all data sent
59
59
60
60
// Unsynchronized buffers, used for lock-free fast path.
61
- inbuf []byte // received data
62
- inbufoff int // bytes of inbuf which have been consumed
61
+ inbuf []byte // received data
62
+ inbufoff int // bytes of inbuf which have been consumed
63
+ outbuf []byte // written data
64
+ outbufoff int // bytes of outbuf which contain data to write
63
65
64
66
// Atomic stream state bits.
65
67
//
@@ -313,7 +315,14 @@ func (s *Stream) Write(b []byte) (n int, err error) {
313
315
if s .IsReadOnly () {
314
316
return 0 , errors .New ("write to read-only stream" )
315
317
}
318
+ if len (b ) > 0 && len (s .outbuf )- s .outbufoff >= len (b ) {
319
+ // Fast path: The data to write fits in s.outbuf.
320
+ copy (s .outbuf [s .outbufoff :], b )
321
+ s .outbufoff += len (b )
322
+ return len (b ), nil
323
+ }
316
324
canWrite := s .outgate .lock ()
325
+ s .flushFastOutputBuffer ()
317
326
for {
318
327
// The first time through this loop, we may or may not be write blocked.
319
328
// We exit the loop after writing all data, so on subsequent passes through
@@ -373,17 +382,51 @@ func (s *Stream) Write(b []byte) (n int, err error) {
373
382
// If we have bytes left to send, we're blocked.
374
383
canWrite = false
375
384
}
385
+ if lim := s .out .start + s .outmaxbuf - s .out .end - 1 ; lim > 0 {
386
+ // If s.out has space allocated and available to be written into,
387
+ // then reference it in s.outbuf for fast-path writes.
388
+ //
389
+ // It's perhaps a bit pointless to limit s.outbuf to the send buffer limit.
390
+ // We've already allocated this buffer so we aren't saving any memory
391
+ // by not using it.
392
+ // For now, we limit it anyway to make it easier to reason about limits.
393
+ //
394
+ // We set the limit to one less than the send buffer limit (the -1 above)
395
+ // so that a write which completely fills the buffer will overflow
396
+ // s.outbuf and trigger a flush.
397
+ s .outbuf = s .out .availableBuffer ()
398
+ if int64 (len (s .outbuf )) > lim {
399
+ s .outbuf = s .outbuf [:lim ]
400
+ }
401
+ }
376
402
s .outUnlock ()
377
403
return n , nil
378
404
}
379
405
380
406
// WriteBytes writes a single byte to the stream.
381
407
func (s * Stream ) WriteByte (c byte ) error {
408
+ if s .outbufoff < len (s .outbuf ) {
409
+ s .outbuf [s .outbufoff ] = c
410
+ s .outbufoff ++
411
+ return nil
412
+ }
382
413
b := [1 ]byte {c }
383
414
_ , err := s .Write (b [:])
384
415
return err
385
416
}
386
417
418
+ func (s * Stream ) flushFastOutputBuffer () {
419
+ if s .outbuf == nil {
420
+ return
421
+ }
422
+ // Commit data previously written to s.outbuf.
423
+ // s.outbuf is a reference to a buffer in s.out, so we just need to record
424
+ // that the output buffer has been extended.
425
+ s .out .end += int64 (s .outbufoff )
426
+ s .outbuf = nil
427
+ s .outbufoff = 0
428
+ }
429
+
387
430
// Flush flushes data written to the stream.
388
431
// It does not wait for the peer to acknowledge receipt of the data.
389
432
// Use Close to wait for the peer's acknowledgement.
@@ -394,6 +437,7 @@ func (s *Stream) Flush() {
394
437
}
395
438
396
439
func (s * Stream ) flushLocked () {
440
+ s .flushFastOutputBuffer ()
397
441
s .outopened .set ()
398
442
if s .outflushed < s .outwin {
399
443
s .outunsent .add (s .outflushed , min (s .outwin , s .out .end ))
@@ -509,6 +553,8 @@ func (s *Stream) resetInternal(code uint64, userClosed bool) {
509
553
// extra RESET_STREAM in this case is harmless.
510
554
s .outreset .set ()
511
555
s .outresetcode = code
556
+ s .outbuf = nil
557
+ s .outbufoff = 0
512
558
s .out .discardBefore (s .out .end )
513
559
s .outunsent = rangeset [int64 ]{}
514
560
s .outblocked .clear ()
0 commit comments