Skip to content

Commit

Permalink
[perf] Concatenate small buffers
Browse files Browse the repository at this point in the history
Make `Sender.frame()` always return a single buffer if the payload
length is less than 126 bytes. This is done by copying the data but
for small buffers it is more efficient than using `socket._writev()`.
  • Loading branch information
lpinca committed Aug 21, 2023
1 parent 8eb2c47 commit 50bfb1a
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 108 deletions.
35 changes: 23 additions & 12 deletions lib/sender.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ class Sender {
if (typeof data === 'string') {
if (
(!options.mask || skipMasking) &&
options[kByteLength] !== undefined
options[kByteLength] !== undefined &&
options[kByteLength] > 125
) {
dataLength = options[kByteLength];
} else {
Expand All @@ -101,6 +102,8 @@ class Sender {
merge = options.mask && options.readOnly && !skipMasking;
}

if (dataLength < 126) merge = true;

let payloadLength = dataLength;

if (dataLength >= 65536) {
Expand All @@ -125,22 +128,30 @@ class Sender {
target.writeUIntBE(dataLength, 4, 6);
}

if (!options.mask) return [target, data];

target[1] |= 0x80;
target[offset - 4] = mask[0];
target[offset - 3] = mask[1];
target[offset - 2] = mask[2];
target[offset - 1] = mask[3];

if (skipMasking) return [target, data];
if (options.mask) {
target[1] |= 0x80;
target[offset - 4] = mask[0];
target[offset - 3] = mask[1];
target[offset - 2] = mask[2];
target[offset - 1] = mask[3];
}

if (merge) {
applyMask(data, mask, target, offset, dataLength);
if (options.mask && !skipMasking) {
applyMask(data, mask, target, offset, dataLength);
} else {
for (let i = 0; i < dataLength; i++) {
target[i + offset] = data[i];
}
}

return [target];
}

applyMask(data, mask, data, 0, dataLength);
if (options.mask && !skipMasking) {
applyMask(data, mask, data, 0, dataLength);
}

return [target, data];
}

Expand Down
155 changes: 60 additions & 95 deletions test/sender.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,31 +57,21 @@ describe('Sender', () => {
fin: true
});

assert.deepStrictEqual(list[0], Buffer.from('8103', 'hex'));
assert.deepStrictEqual(list[1], Buffer.from('e282ac', 'hex'));
assert.deepStrictEqual(list[0], Buffer.from('8103e282ac', 'hex'));
});
});

describe('#send', () => {
it('compresses data if compress option is enabled', (done) => {
const chunks = [];
const perMessageDeflate = new PerMessageDeflate();
let count = 0;
const expected = Buffer.from('c104cac80400', 'hex');
const mockSocket = new MockSocket({
write: (chunk) => {
chunks.push(chunk);
if (chunks.length !== 6) return;

assert.strictEqual(chunks[0].length, 2);
assert.strictEqual(chunks[0][0] & 0x40, 0x40);

assert.strictEqual(chunks[2].length, 2);
assert.strictEqual(chunks[2][0] & 0x40, 0x40);

assert.strictEqual(chunks[4].length, 2);
assert.strictEqual(chunks[4][0] & 0x40, 0x40);
done();
assert.deepStrictEqual(chunk, expected);
if (++count === 3) done();
}
});
const perMessageDeflate = new PerMessageDeflate();
const sender = new Sender(mockSocket, {
'permessage-deflate': perMessageDeflate
});
Expand All @@ -98,16 +88,10 @@ describe('Sender', () => {

describe('when context takeover is disabled', () => {
it('honors the compression threshold', (done) => {
const chunks = [];
const perMessageDeflate = new PerMessageDeflate();
const mockSocket = new MockSocket({
write: (chunk) => {
chunks.push(chunk);
if (chunks.length !== 2) return;

assert.strictEqual(chunks[0].length, 2);
assert.notStrictEqual(chunk[0][0] & 0x40, 0x40);
assert.strictEqual(chunks[1], 'hi');
assert.deepStrictEqual(chunk, Buffer.from('81026869', 'hex'));
done();
}
});
Expand All @@ -124,23 +108,21 @@ describe('Sender', () => {
});

it('compresses all fragments of a fragmented message', (done) => {
const chunks = [];
const perMessageDeflate = new PerMessageDeflate({ threshold: 3 });
let count = 0;
const mockSocket = new MockSocket({
write: (chunk) => {
chunks.push(chunk);
if (chunks.length !== 4) return;

assert.strictEqual(chunks[0].length, 2);
assert.strictEqual(chunks[0][0] & 0x40, 0x40);
assert.strictEqual(chunks[1].length, 9);

assert.strictEqual(chunks[2].length, 2);
assert.strictEqual(chunks[2][0] & 0x40, 0x00);
assert.strictEqual(chunks[3].length, 4);
done();
if (++count === 1) {
assert.deepStrictEqual(
chunk,
Buffer.from('410932343206000000ffff', 'hex')
);
} else {
assert.deepStrictEqual(chunk, Buffer.from('800432340200', 'hex'));
done();
}
}
});
const perMessageDeflate = new PerMessageDeflate({ threshold: 3 });
const sender = new Sender(mockSocket, {
'permessage-deflate': perMessageDeflate
});
Expand All @@ -155,23 +137,18 @@ describe('Sender', () => {
});

it('does not compress any fragments of a fragmented message', (done) => {
const chunks = [];
const perMessageDeflate = new PerMessageDeflate({ threshold: 3 });
let count = 0;
const mockSocket = new MockSocket({
write: (chunk) => {
chunks.push(chunk);
if (chunks.length !== 4) return;

assert.strictEqual(chunks[0].length, 2);
assert.strictEqual(chunks[0][0] & 0x40, 0x00);
assert.strictEqual(chunks[1].length, 2);

assert.strictEqual(chunks[2].length, 2);
assert.strictEqual(chunks[2][0] & 0x40, 0x00);
assert.strictEqual(chunks[3].length, 3);
done();
if (++count === 1) {
assert.deepStrictEqual(chunk, Buffer.from('01023132', 'hex'));
} else {
assert.deepStrictEqual(chunk, Buffer.from('8003313233', 'hex'));
done();
}
}
});
const perMessageDeflate = new PerMessageDeflate({ threshold: 3 });
const sender = new Sender(mockSocket, {
'permessage-deflate': perMessageDeflate
});
Expand All @@ -186,23 +163,24 @@ describe('Sender', () => {
});

it('compresses empty buffer as first fragment', (done) => {
const chunks = [];
const perMessageDeflate = new PerMessageDeflate({ threshold: 0 });
let count = 0;
const mockSocket = new MockSocket({
write: (chunk) => {
chunks.push(chunk);
if (chunks.length !== 4) return;

assert.strictEqual(chunks[0].length, 2);
assert.strictEqual(chunks[0][0] & 0x40, 0x40);
assert.strictEqual(chunks[1].length, 5);

assert.strictEqual(chunks[2].length, 2);
assert.strictEqual(chunks[2][0] & 0x40, 0x00);
assert.strictEqual(chunks[3].length, 6);
done();
if (++count === 1) {
assert.deepStrictEqual(
chunk,
Buffer.from('4105000000ffff', 'hex')
);
} else {
assert.deepStrictEqual(
chunk,
Buffer.from('80064a492c490400', 'hex')
);
done();
}
}
});
const perMessageDeflate = new PerMessageDeflate({ threshold: 0 });
const sender = new Sender(mockSocket, {
'permessage-deflate': perMessageDeflate
});
Expand All @@ -217,23 +195,21 @@ describe('Sender', () => {
});

it('compresses empty buffer as last fragment', (done) => {
const chunks = [];
const perMessageDeflate = new PerMessageDeflate({ threshold: 0 });
let count = 0;
const mockSocket = new MockSocket({
write: (chunk) => {
chunks.push(chunk);
if (chunks.length !== 4) return;

assert.strictEqual(chunks[0].length, 2);
assert.strictEqual(chunks[0][0] & 0x40, 0x40);
assert.strictEqual(chunks[1].length, 10);

assert.strictEqual(chunks[2].length, 2);
assert.strictEqual(chunks[2][0] & 0x40, 0x00);
assert.strictEqual(chunks[3].length, 1);
done();
if (++count === 1) {
assert.deepStrictEqual(
chunk,
Buffer.from('410a4a492c4904000000ffff', 'hex')
);
} else {
assert.deepStrictEqual(chunk, Buffer.from('800100', 'hex'));
done();
}
}
});
const perMessageDeflate = new PerMessageDeflate({ threshold: 0 });
const sender = new Sender(mockSocket, {
'permessage-deflate': perMessageDeflate
});
Expand All @@ -255,15 +231,9 @@ describe('Sender', () => {
let count = 0;
const mockSocket = new MockSocket({
write: (data) => {
if (++count < 3) return;

if (count % 2) {
assert.ok(data.equals(Buffer.from([0x89, 0x02])));
} else if (count < 8) {
assert.ok(data.equals(Buffer.from([0x68, 0x69])));
} else {
assert.strictEqual(data, 'hi');
done();
if (++count > 1) {
assert.deepStrictEqual(data, Buffer.from('89026869', 'hex'));
if (count === 4) done();
}
}
});
Expand All @@ -288,16 +258,11 @@ describe('Sender', () => {
let count = 0;
const mockSocket = new MockSocket({
write: (data) => {
if (++count < 3) return;

if (count % 2) {
assert.ok(data.equals(Buffer.from([0x8a, 0x02])));
} else if (count < 8) {
assert.ok(data.equals(Buffer.from([0x68, 0x69])));
} else {
assert.strictEqual(data, 'hi');
done();
}
if (++count === 1) return;

assert.deepStrictEqual(data, Buffer.from('8a026869', 'hex'));

if (count === 4) done();
}
});
const sender = new Sender(mockSocket, {
Expand Down Expand Up @@ -362,7 +327,7 @@ describe('Sender', () => {
sender.send('baz', { compress: true, fin: true });

sender.close(1000, undefined, false, () => {
assert.strictEqual(count, 8);
assert.strictEqual(count, 4);
done();
});
});
Expand Down
2 changes: 1 addition & 1 deletion test/websocket.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3992,7 +3992,7 @@ describe('WebSocket', () => {
];

for (let i = 0; i < 399; i++) {
list.push(list[list.length - 2], list[list.length - 1]);
list.push(list[list.length - 1]);
}

// This hack is used because there is no guarantee that more than
Expand Down

0 comments on commit 50bfb1a

Please sign in to comment.