Skip to content

Commit 751a0ed

Browse files
authored
Merge pull request #72 from nhooyr/bench2
Improve benchmarks and single frame write path
2 parents 2389eb1 + cb5dba8 commit 751a0ed

File tree

6 files changed

+203
-137
lines changed

6 files changed

+203
-137
lines changed

Diff for: bench_test.go

-104
This file was deleted.

Diff for: export_test.go

+6-6
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ import (
88
// method for when the entire message is in memory and does not need to be streamed
99
// to the peer via Writer.
1010
//
11-
// Both paths are zero allocation but Writer always has
12-
// to write an additional fin frame when Close is called on the writer which
13-
// can result in worse performance if the full message exceeds the buffer size
14-
// which is 4096 right now as then two syscalls will be necessary to complete the message.
15-
// TODO this is no good as we cannot write data frame msg in between other ones
11+
// This prevents the allocation of the Writer.
12+
// Furthermore Writer always has to write an additional fin frame when Close is
13+
// called on the writer which can result in worse performance if the full message
14+
// exceeds the buffer size which is 4096 right now as then an extra syscall
15+
// will be necessary to complete the message.
1616
func (c *Conn) Write(ctx context.Context, typ MessageType, p []byte) error {
17-
return c.writeControl(ctx, opcode(typ), p)
17+
return c.writeSingleFrame(ctx, opcode(typ), p)
1818
}

Diff for: websocket.go

+27-15
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313
"golang.org/x/xerrors"
1414
)
1515

16-
type control struct {
16+
type frame struct {
1717
opcode opcode
1818
payload []byte
1919
}
@@ -42,7 +42,8 @@ type Conn struct {
4242
// ping on writeDone.
4343
// writeDone will be closed if the data message write errors.
4444
write chan MessageType
45-
control chan control
45+
control chan frame
46+
fastWrite chan frame
4647
writeBytes chan []byte
4748
writeDone chan struct{}
4849
writeFlush chan struct{}
@@ -86,7 +87,8 @@ func (c *Conn) init() {
8687
c.closed = make(chan struct{})
8788

8889
c.write = make(chan MessageType)
89-
c.control = make(chan control)
90+
c.control = make(chan frame)
91+
c.fastWrite = make(chan frame)
9092
c.writeBytes = make(chan []byte)
9193
c.writeDone = make(chan struct{})
9294
c.writeFlush = make(chan struct{})
@@ -103,6 +105,8 @@ func (c *Conn) init() {
103105
go c.readLoop()
104106
}
105107

108+
// We never mask inside here because our mask key is always 0,0,0,0.
109+
// See comment on secWebSocketKey.
106110
func (c *Conn) writeFrame(h header, p []byte) {
107111
b2 := marshalHeader(h)
108112
_, err := c.bw.Write(b2)
@@ -126,14 +130,14 @@ func (c *Conn) writeFrame(h header, p []byte) {
126130
}
127131
}
128132

129-
func (c *Conn) writeLoopControl(control control) {
133+
func (c *Conn) writeLoopFastWrite(frame frame) {
130134
h := header{
131135
fin: true,
132-
opcode: control.opcode,
133-
payloadLength: int64(len(control.payload)),
136+
opcode: frame.opcode,
137+
payloadLength: int64(len(frame.payload)),
134138
masked: c.client,
135139
}
136-
c.writeFrame(h, control.payload)
140+
c.writeFrame(h, frame.payload)
137141
select {
138142
case <-c.closed:
139143
case c.writeDone <- struct{}{}:
@@ -150,7 +154,11 @@ messageLoop:
150154
case <-c.closed:
151155
return
152156
case control := <-c.control:
153-
c.writeLoopControl(control)
157+
c.writeLoopFastWrite(control)
158+
continue
159+
case frame := <-c.fastWrite:
160+
c.writeLoopFastWrite(frame)
161+
continue
154162
case dataType = <-c.write:
155163
}
156164

@@ -160,7 +168,7 @@ messageLoop:
160168
case <-c.closed:
161169
return
162170
case control := <-c.control:
163-
c.writeLoopControl(control)
171+
c.writeLoopFastWrite(control)
164172
case b := <-c.writeBytes:
165173
h := header{
166174
fin: false,
@@ -220,7 +228,7 @@ func (c *Conn) handleControl(h header) {
220228
}
221229

222230
if h.masked {
223-
xor(h.maskKey, 0, b)
231+
fastXOR(h.maskKey, 0, b)
224232
}
225233

226234
switch h.opcode {
@@ -314,7 +322,7 @@ func (c *Conn) dataReadLoop(h header) (err error) {
314322
left -= int64(len(b))
315323

316324
if h.masked {
317-
maskPos = xor(h.maskKey, maskPos, b)
325+
maskPos = fastXOR(h.maskKey, maskPos, b)
318326
}
319327

320328
// Must set this before we signal the read is done.
@@ -341,7 +349,7 @@ func (c *Conn) writePong(p []byte) error {
341349
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
342350
defer cancel()
343351

344-
err := c.writeControl(ctx, opPong, p)
352+
err := c.writeSingleFrame(ctx, opPong, p)
345353
return err
346354
}
347355

@@ -384,7 +392,7 @@ func (c *Conn) writeClose(p []byte, cerr CloseError) error {
384392
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
385393
defer cancel()
386394

387-
err := c.writeControl(ctx, opClose, p)
395+
err := c.writeSingleFrame(ctx, opClose, p)
388396

389397
c.close(cerr)
390398

@@ -399,11 +407,15 @@ func (c *Conn) writeClose(p []byte, cerr CloseError) error {
399407
return nil
400408
}
401409

402-
func (c *Conn) writeControl(ctx context.Context, opcode opcode, p []byte) error {
410+
func (c *Conn) writeSingleFrame(ctx context.Context, opcode opcode, p []byte) error {
411+
ch := c.fastWrite
412+
if opcode.controlOp() {
413+
ch = c.control
414+
}
403415
select {
404416
case <-c.closed:
405417
return c.closeErr
406-
case c.control <- control{
418+
case ch <- frame{
407419
opcode: opcode,
408420
payload: p,
409421
}:

0 commit comments

Comments
 (0)