Skip to content

Commit 4014ecb

Browse files
zbjornsonFishrock123
authored andcommitted
buffer: speed up swap16/32, add swap64
* Speed up buffer.swap16 and swap32 by using builtins. Up to ~6x gain. Drop transition point between JS and C++ implementations accordingly. Amount of performance improvement not only depends on buffer size but also memory alignment. * Fix tests: C++ impl tests were testing 0-filled buffers so were always passing. * Add similar buffer.swap64 method. * Make buffer-swap benchmark mirror JS impl. doc/api/buffer.markdown has an entry of "added: REPLACEME" that should be changed to the correct release number before tagged. Because node is currently using a very old version of cpplint.py it doesn't know that std::swap() has moved from <algorithm> to <utility> in c++11. So until cpplint.py is updated simply NOLINT the line. Technically it should be NOLINT(build/include_what_you_use), but that puts the line over 80 characters causing another lint error. PR-URL: #7157 Reviewed-By: Trevor Norris <trev.norris@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Backport-URL: #7546
1 parent 63d361b commit 4014ecb

File tree

5 files changed

+351
-111
lines changed

5 files changed

+351
-111
lines changed

benchmark/buffers/buffer-swap.js

Lines changed: 52 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,90 @@
11
'use strict';
22

33
const common = require('../common.js');
4+
const v8 = require('v8');
45

56
const bench = common.createBenchmark(main, {
6-
method: ['swap16', 'swap32', 'htons', 'htonl'],
7-
len: [4, 64, 512, 768, 1024, 1536, 2056, 4096, 8192],
8-
n: [1e6]
7+
aligned: ['true', 'false'],
8+
method: ['swap16', 'swap32', 'swap64'/*, 'htons', 'htonl', 'htonll'*/],
9+
len: [8, 64, 128, 256, 512, 768, 1024, 1536, 2056, 4096, 8192],
10+
n: [5e7]
911
});
1012

1113
// The htons and htonl methods below are used to benchmark the
1214
// performance difference between doing the byteswap in pure
1315
// javascript regardless of Buffer size as opposed to dropping
14-
// down to the native layer for larger Buffer sizes.
16+
// down to the native layer for larger Buffer sizes. Commented
17+
// out by default because they are slow for big buffers. If
18+
// re-evaluating the crossover point, uncomment those methods
19+
// and comment out their implementations in lib/buffer.js so
20+
// C++ version will always be used.
21+
22+
function swap(b, n, m) {
23+
const i = b[n];
24+
b[n] = b[m];
25+
b[m] = i;
26+
}
1527

1628
Buffer.prototype.htons = function htons() {
1729
if (this.length % 2 !== 0)
1830
throw new RangeError();
19-
for (var i = 0, n = 0; i < this.length; i += 2) {
20-
n = this[i];
21-
this[i] = this[i + 1];
22-
this[i + 1] = n;
31+
for (var i = 0; i < this.length; i += 2) {
32+
swap(this, i, i + 1);
2333
}
2434
return this;
2535
};
2636

2737
Buffer.prototype.htonl = function htonl() {
28-
if (this.length % 2 !== 0)
38+
if (this.length % 4 !== 0)
39+
throw new RangeError();
40+
for (var i = 0; i < this.length; i += 4) {
41+
swap(this, i, i + 3);
42+
swap(this, i + 1, i + 2);
43+
}
44+
return this;
45+
};
46+
47+
Buffer.prototype.htonll = function htonl() {
48+
if (this.length % 8 !== 0)
2949
throw new RangeError();
30-
for (var i = 0, n = 0; i < this.length; i += 4) {
31-
n = this[i];
32-
this[i] = this[i + 3];
33-
this[i + 3] = n;
34-
n = this[i + 1];
35-
this[i + 1] = this[i + 2];
36-
this[i + 2] = n;
50+
for (var i = 0; i < this.length; i += 8) {
51+
swap(this, i, i + 7);
52+
swap(this, i + 1, i + 6);
53+
swap(this, i + 2, i + 5);
54+
swap(this, i + 3, i + 4);
3755
}
3856
return this;
3957
};
4058

41-
function createBuffer(len) {
59+
function createBuffer(len, aligned) {
60+
len += aligned ? 0 : 1;
4261
const buf = Buffer.allocUnsafe(len);
4362
for (var i = 1; i <= len; i++)
4463
buf[i - 1] = i;
45-
return buf;
64+
return aligned ? buf : buf.slice(1);
4665
}
4766

48-
function bufferSwap(n, buf, method) {
49-
for (var i = 1; i <= n; i++)
50-
buf[method]();
67+
function genMethod(method) {
68+
const fnString =
69+
'return function ' + method + '(n, buf) {' +
70+
' for (var i = 0; i <= n; i++)' +
71+
' buf.' + method + '();' +
72+
'}';
73+
return (new Function(fnString))();
5174
}
5275

5376
function main(conf) {
5477
const method = conf.method;
5578
const len = conf.len | 0;
5679
const n = conf.n | 0;
57-
const buf = createBuffer(len);
80+
const aligned = conf.aligned || 'true';
81+
const buf = createBuffer(len, aligned === 'true');
82+
const bufferSwap = genMethod(method);
83+
84+
v8.setFlagsFromString('--allow_natives_syntax');
85+
eval('%OptimizeFunctionOnNextCall(bufferSwap)');
86+
5887
bench.start();
59-
bufferSwap(n, buf, method);
88+
bufferSwap(n, buf);
6089
bench.end(n);
6190
}

doc/api/buffer.md

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1461,10 +1461,10 @@ calls can be chained.
14611461
```js
14621462
const buf = Buffer.from([0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8]);
14631463
console.log(buf);
1464-
// Prints Buffer(0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8)
1464+
// Prints <Buffer 01 02 03 04 05 06 07 08>
14651465
buf.swap16();
14661466
console.log(buf);
1467-
// Prints Buffer(0x2, 0x1, 0x4, 0x3, 0x6, 0x5, 0x8, 0x7)
1467+
// Prints <Buffer 02 01 04 03 06 05 08 07>
14681468
```
14691469

14701470
### buf.swap32()
@@ -1482,12 +1482,36 @@ calls can be chained.
14821482
```js
14831483
const buf = Buffer.from([0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8]);
14841484
console.log(buf);
1485-
// Prints Buffer(0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8)
1485+
// Prints <Buffer 01 02 03 04 05 06 07 08>
14861486
buf.swap32();
14871487
console.log(buf);
1488-
// Prints Buffer(0x4, 0x3, 0x2, 0x1, 0x8, 0x7, 0x6, 0x5)
1488+
// Prints <Buffer 04 03 02 01 08 07 06 05>
14891489
```
14901490

1491+
### buf.swap64()
1492+
<!-- YAML
1493+
added: REPLACEME
1494+
-->
1495+
1496+
* Return: {Buffer}
1497+
1498+
Interprets the `Buffer` as an array of 64-bit numbers and swaps
1499+
the byte-order *in-place*. Throws a `RangeError` if the `Buffer` length is
1500+
not a multiple of 64 bits. The method returns a reference to the Buffer, so
1501+
calls can be chained.
1502+
1503+
```js
1504+
const buf = Buffer.from([0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8]);
1505+
console.log(buf);
1506+
// Prints <Buffer 01 02 03 04 05 06 07 08>
1507+
buf.swap64();
1508+
console.log(buf);
1509+
// Prints <Buffer 08 07 06 05 04 03 02 01>
1510+
```
1511+
1512+
Note that JavaScript cannot encode 64-bit integers. This method is intended
1513+
for working with 64-bit floats.
1514+
14911515
### buf.toString([encoding[, start[, end]]])
14921516

14931517
* `encoding` {String} Default: `'utf8'`

lib/buffer.js

Lines changed: 64 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -24,47 +24,6 @@ var poolSize, poolOffset, allocPool;
2424

2525
binding.setupBufferJS(Buffer.prototype, bindingObj);
2626

27-
const swap16n = binding.swap16;
28-
const swap32n = binding.swap32;
29-
30-
function swap(b, n, m) {
31-
const i = b[n];
32-
b[n] = b[m];
33-
b[m] = i;
34-
}
35-
36-
Buffer.prototype.swap16 = function swap16() {
37-
// For Buffer.length < 512, it's generally faster to
38-
// do the swap in javascript. For larger buffers,
39-
// dropping down to the native code is faster.
40-
const len = this.length;
41-
if (len % 2 !== 0)
42-
throw new RangeError('Buffer size must be a multiple of 16-bits');
43-
if (len < 512) {
44-
for (var i = 0; i < len; i += 2)
45-
swap(this, i, i + 1);
46-
return this;
47-
}
48-
return swap16n.apply(this);
49-
};
50-
51-
Buffer.prototype.swap32 = function swap32() {
52-
// For Buffer.length < 1024, it's generally faster to
53-
// do the swap in javascript. For larger buffers,
54-
// dropping down to the native code is faster.
55-
const len = this.length;
56-
if (len % 4 !== 0)
57-
throw new RangeError('Buffer size must be a multiple of 32-bits');
58-
if (len < 1024) {
59-
for (var i = 0; i < len; i += 4) {
60-
swap(this, i, i + 3);
61-
swap(this, i + 1, i + 2);
62-
}
63-
return this;
64-
}
65-
return swap32n.apply(this);
66-
};
67-
6827
const flags = bindingObj.flags;
6928
const kNoZeroFill = 0;
7029

@@ -1320,3 +1279,67 @@ Buffer.prototype.writeDoubleBE = function writeDoubleBE(val, offset, noAssert) {
13201279
binding.writeDoubleBE(this, val, offset, true);
13211280
return offset + 8;
13221281
};
1282+
1283+
const swap16n = binding.swap16;
1284+
const swap32n = binding.swap32;
1285+
const swap64n = binding.swap64;
1286+
1287+
function swap(b, n, m) {
1288+
const i = b[n];
1289+
b[n] = b[m];
1290+
b[m] = i;
1291+
}
1292+
1293+
1294+
Buffer.prototype.swap16 = function swap16() {
1295+
// For Buffer.length < 128, it's generally faster to
1296+
// do the swap in javascript. For larger buffers,
1297+
// dropping down to the native code is faster.
1298+
const len = this.length;
1299+
if (len % 2 !== 0)
1300+
throw new RangeError('Buffer size must be a multiple of 16-bits');
1301+
if (len < 128) {
1302+
for (var i = 0; i < len; i += 2)
1303+
swap(this, i, i + 1);
1304+
return this;
1305+
}
1306+
return swap16n(this);
1307+
};
1308+
1309+
1310+
Buffer.prototype.swap32 = function swap32() {
1311+
// For Buffer.length < 192, it's generally faster to
1312+
// do the swap in javascript. For larger buffers,
1313+
// dropping down to the native code is faster.
1314+
const len = this.length;
1315+
if (len % 4 !== 0)
1316+
throw new RangeError('Buffer size must be a multiple of 32-bits');
1317+
if (len < 192) {
1318+
for (var i = 0; i < len; i += 4) {
1319+
swap(this, i, i + 3);
1320+
swap(this, i + 1, i + 2);
1321+
}
1322+
return this;
1323+
}
1324+
return swap32n(this);
1325+
};
1326+
1327+
1328+
Buffer.prototype.swap64 = function swap64() {
1329+
// For Buffer.length < 192, it's generally faster to
1330+
// do the swap in javascript. For larger buffers,
1331+
// dropping down to the native code is faster.
1332+
const len = this.length;
1333+
if (len % 8 !== 0)
1334+
throw new RangeError('Buffer size must be a multiple of 64-bits');
1335+
if (len < 192) {
1336+
for (var i = 0; i < len; i += 8) {
1337+
swap(this, i, i + 7);
1338+
swap(this, i + 1, i + 6);
1339+
swap(this, i + 2, i + 5);
1340+
swap(this, i + 3, i + 4);
1341+
}
1342+
return this;
1343+
}
1344+
return swap64n(this);
1345+
};

0 commit comments

Comments
 (0)