Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 4014ecb

Browse filesBrowse 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
Copy full SHA for 4014ecb

File tree

Expand file treeCollapse file tree

5 files changed

+351
-111
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

5 files changed

+351
-111
lines changed
Open diff view settings
Collapse file

‎benchmark/buffers/buffer-swap.js‎

Copy file name to clipboard
+52-23Lines 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
}
Collapse file

‎doc/api/buffer.md‎

Copy file name to clipboardExpand all lines: doc/api/buffer.md
+28-4Lines changed: 28 additions & 4 deletions
  • Display the source diff
  • Display the rich diff
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'`
Collapse file

‎lib/buffer.js‎

Copy file name to clipboardExpand all lines: lib/buffer.js
+64-41Lines 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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.