Skip to content

Commit c0dc3dd

Browse files
tsctxUzlopak
andauthored
websocket: 200x faster generate mask (nodejs#3204)
* websocket: improve performance of generate mask * apply comments * Apply suggestions from code review * Apply suggestions from code review * Apply suggestions from code review Co-authored-by: Aras Abbasi <[email protected]> * Apply suggestions from code review * Update lib/web/websocket/frame.js * Update lib/web/websocket/frame.js * Update lib/web/websocket/frame.js Co-authored-by: Aras Abbasi <[email protected]> --------- Co-authored-by: Aras Abbasi <[email protected]>
1 parent ee64eb5 commit c0dc3dd

File tree

2 files changed

+52
-8
lines changed

2 files changed

+52
-8
lines changed
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { randomFillSync, randomBytes } from 'node:crypto'
2+
import { bench, group, run } from 'mitata'
3+
4+
const BUFFER_SIZE = 16384
5+
6+
const buf = Buffer.allocUnsafe(BUFFER_SIZE)
7+
let bufIdx = BUFFER_SIZE
8+
9+
function generateMask () {
10+
if (bufIdx === BUFFER_SIZE) {
11+
bufIdx = 0
12+
randomFillSync(buf, 0, BUFFER_SIZE)
13+
}
14+
return [buf[bufIdx++], buf[bufIdx++], buf[bufIdx++], buf[bufIdx++]]
15+
}
16+
17+
group('generate', () => {
18+
bench('generateMask', () => generateMask())
19+
bench('crypto.randomBytes(4)', () => randomBytes(4))
20+
})
21+
22+
await run()

lib/web/websocket/frame.js

+30-8
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,34 @@
22

33
const { maxUnsigned16Bit } = require('./constants')
44

5+
const BUFFER_SIZE = 16386
6+
57
/** @type {import('crypto')} */
68
let crypto
9+
let buffer = null
10+
let bufIdx = BUFFER_SIZE
11+
712
try {
813
crypto = require('node:crypto')
914
/* c8 ignore next 3 */
1015
} catch {
16+
crypto = {
17+
// not full compatibility, but minimum.
18+
randomFillSync: function randomFillSync (buffer, _offset, _size) {
19+
for (let i = 0; i < buffer.length; ++i) {
20+
buffer[i] = Math.random() * 255 | 0
21+
}
22+
return buffer
23+
}
24+
}
25+
}
1126

27+
function generateMask () {
28+
if (bufIdx === BUFFER_SIZE) {
29+
bufIdx = 0
30+
crypto.randomFillSync((buffer ??= Buffer.allocUnsafe(BUFFER_SIZE)), 0, BUFFER_SIZE)
31+
}
32+
return [buffer[bufIdx++], buffer[bufIdx++], buffer[bufIdx++], buffer[bufIdx++]]
1233
}
1334

1435
class WebsocketFrameSend {
@@ -17,11 +38,12 @@ class WebsocketFrameSend {
1738
*/
1839
constructor (data) {
1940
this.frameData = data
20-
this.maskKey = crypto.randomBytes(4)
2141
}
2242

2343
createFrame (opcode) {
24-
const bodyLength = this.frameData?.byteLength ?? 0
44+
const frameData = this.frameData
45+
const maskKey = generateMask()
46+
const bodyLength = frameData?.byteLength ?? 0
2547

2648
/** @type {number} */
2749
let payloadLength = bodyLength // 0-125
@@ -43,10 +65,10 @@ class WebsocketFrameSend {
4365
buffer[0] = (buffer[0] & 0xF0) + opcode // opcode
4466

4567
/*! ws. MIT License. Einar Otto Stangvik <[email protected]> */
46-
buffer[offset - 4] = this.maskKey[0]
47-
buffer[offset - 3] = this.maskKey[1]
48-
buffer[offset - 2] = this.maskKey[2]
49-
buffer[offset - 1] = this.maskKey[3]
68+
buffer[offset - 4] = maskKey[0]
69+
buffer[offset - 3] = maskKey[1]
70+
buffer[offset - 2] = maskKey[2]
71+
buffer[offset - 1] = maskKey[3]
5072

5173
buffer[1] = payloadLength
5274

@@ -61,8 +83,8 @@ class WebsocketFrameSend {
6183
buffer[1] |= 0x80 // MASK
6284

6385
// mask body
64-
for (let i = 0; i < bodyLength; i++) {
65-
buffer[offset + i] = this.frameData[i] ^ this.maskKey[i % 4]
86+
for (let i = 0; i < bodyLength; ++i) {
87+
buffer[offset + i] = frameData[i] ^ maskKey[i & 3]
6688
}
6789

6890
return buffer

0 commit comments

Comments
 (0)