Skip to content

Commit 77f1107

Browse files
committed
Improve mask performance
1 parent 5df680c commit 77f1107

File tree

4 files changed

+134
-34
lines changed

4 files changed

+134
-34
lines changed

bench_test.go

Lines changed: 0 additions & 19 deletions
This file was deleted.

conn.go

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"errors"
1111
"io"
1212
"io/ioutil"
13-
"math/rand"
1413
"net"
1514
"strconv"
1615
"time"
@@ -218,20 +217,6 @@ func isValidReceivedCloseCode(code int) bool {
218217
return validReceivedCloseCodes[code] || (code >= 3000 && code <= 4999)
219218
}
220219

221-
func maskBytes(key [4]byte, pos int, b []byte) int {
222-
for i := range b {
223-
b[i] ^= key[pos&3]
224-
pos++
225-
}
226-
return pos & 3
227-
}
228-
229-
func newMaskKey() [4]byte {
230-
n := rand.Uint32()
231-
return [4]byte{byte(n), byte(n >> 8), byte(n >> 16), byte(n >> 24)}
232-
}
233-
234-
// Conn represents a WebSocket connection.
235220
type Conn struct {
236221
conn net.Conn
237222
isServer bool

mask.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. Use of
2+
// this source code is governed by a BSD-style license that can be found in the
3+
// LICENSE file.
4+
5+
package websocket
6+
7+
import (
8+
"math/rand"
9+
"unsafe"
10+
)
11+
12+
const wordSize = int(unsafe.Sizeof(uintptr(0)))
13+
14+
func newMaskKey() [4]byte {
15+
n := rand.Uint32()
16+
return [4]byte{byte(n), byte(n >> 8), byte(n >> 16), byte(n >> 24)}
17+
}
18+
19+
func maskBytes(key [4]byte, pos int, b []byte) int {
20+
21+
// Mask one byte at a time for small buffers.
22+
if len(b) < 2*wordSize {
23+
for i := range b {
24+
b[i] ^= key[pos&3]
25+
pos++
26+
}
27+
return pos & 3
28+
}
29+
30+
// Mask one byte at a time to word boundary.
31+
if n := int(uintptr(unsafe.Pointer(&b))) % wordSize; n != 0 {
32+
n = wordSize - n
33+
for i := range b[:n] {
34+
b[i] ^= key[pos&3]
35+
pos++
36+
}
37+
b = b[n:]
38+
}
39+
40+
// Create aligned word size key.
41+
var k [wordSize]byte
42+
for i := range k {
43+
k[i] = key[(pos+i)&3]
44+
}
45+
kw := *(*uintptr)(unsafe.Pointer(&k))
46+
47+
// Mask one word at a time.
48+
n := (len(b) / wordSize) * wordSize
49+
for i := 0; i < n; i += wordSize {
50+
*(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + uintptr(i))) ^= kw
51+
}
52+
53+
// Mask one byte at a time for remaining bytes.
54+
b = b[n:]
55+
for i := range b {
56+
b[i] ^= key[pos&3]
57+
pos++
58+
}
59+
60+
return pos & 3
61+
}

mask_test.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. Use of
2+
// this source code is governed by a BSD-style license that can be found in the
3+
// LICENSE file.
4+
5+
// Require 1.7 for sub-bencmarks
6+
// +build go1.7
7+
8+
package websocket
9+
10+
import (
11+
"fmt"
12+
"testing"
13+
)
14+
15+
func maskBytesByByte(key [4]byte, pos int, b []byte) int {
16+
for i := range b {
17+
b[i] ^= key[pos&3]
18+
pos++
19+
}
20+
return pos & 3
21+
}
22+
23+
func notzero(b []byte) int {
24+
for i := range b {
25+
if b[i] != 0 {
26+
return i
27+
}
28+
}
29+
return -1
30+
}
31+
32+
func TestMaskBytes(t *testing.T) {
33+
key := [4]byte{1, 2, 3, 4}
34+
for size := 1; size <= 1024; size++ {
35+
for align := 0; align < wordSize; align++ {
36+
for pos := 0; pos < 4; pos++ {
37+
b := make([]byte, size+align)[align:]
38+
maskBytes(key, pos, b)
39+
maskBytesByByte(key, pos, b)
40+
if i := notzero(b); i >= 0 {
41+
t.Errorf("size:%d, align:%d, pos:%d, offset:%d", size, align, pos, i)
42+
}
43+
}
44+
}
45+
}
46+
}
47+
48+
func BenchmarkMaskBytes(b *testing.B) {
49+
for _, size := range []int{2, 4, 8, 16, 32, 512, 1024} {
50+
b.Run(fmt.Sprintf("size-%d", size), func(b *testing.B) {
51+
for _, align := range []int{wordSize / 2} {
52+
b.Run(fmt.Sprintf("align-%d", align), func(b *testing.B) {
53+
for _, fn := range []struct {
54+
name string
55+
fn func(key [4]byte, pos int, b []byte) int
56+
}{
57+
{"byte", maskBytesByByte},
58+
{"word", maskBytes},
59+
} {
60+
b.Run(fn.name, func(b *testing.B) {
61+
key := newMaskKey()
62+
data := make([]byte, size+align)[align:]
63+
for i := 0; i < b.N; i++ {
64+
fn.fn(key, 0, data)
65+
}
66+
b.SetBytes(int64(len(data)))
67+
})
68+
}
69+
})
70+
}
71+
})
72+
}
73+
}

0 commit comments

Comments
 (0)