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

5 files changed

Lines changed: 351 additions & 111 deletions

File tree

‎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
constcommon=require('../common.js');
4+
constv8=require('v8');
45

56
constbench=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+
functionswap(b,n,m){
23+
consti=b[n];
24+
b[n]=b[m];
25+
b[m]=i;
26+
}
1527

1628
Buffer.prototype.htons=functionhtons(){
1729
if(this.length%2!==0)
1830
thrownewRangeError();
19-
for(vari=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(vari=0;i<this.length;i+=2){
32+
swap(this,i,i+1);
2333
}
2434
returnthis;
2535
};
2636

2737
Buffer.prototype.htonl=functionhtonl(){
28-
if(this.length%2!==0)
38+
if(this.length%4!==0)
39+
thrownewRangeError();
40+
for(vari=0;i<this.length;i+=4){
41+
swap(this,i,i+3);
42+
swap(this,i+1,i+2);
43+
}
44+
returnthis;
45+
};
46+
47+
Buffer.prototype.htonll=functionhtonl(){
48+
if(this.length%8!==0)
2949
thrownewRangeError();
30-
for(vari=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(vari=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
returnthis;
3957
};
4058

41-
functioncreateBuffer(len){
59+
functioncreateBuffer(len,aligned){
60+
len+=aligned ? 0 : 1;
4261
constbuf=Buffer.allocUnsafe(len);
4362
for(vari=1;i<=len;i++)
4463
buf[i-1]=i;
45-
returnbuf;
64+
returnaligned ? buf : buf.slice(1);
4665
}
4766

48-
functionbufferSwap(n,buf,method){
49-
for(vari=1;i<=n;i++)
50-
buf[method]();
67+
functiongenMethod(method){
68+
constfnString=
69+
'return function '+method+'(n, buf) {'+
70+
' for (var i = 0; i <= n; i++)'+
71+
' buf.'+method+'();'+
72+
'}';
73+
return(newFunction(fnString))();
5174
}
5275

5376
functionmain(conf){
5477
constmethod=conf.method;
5578
constlen=conf.len|0;
5679
constn=conf.n|0;
57-
constbuf=createBuffer(len);
80+
constaligned=conf.aligned||'true';
81+
constbuf=createBuffer(len,aligned==='true');
82+
constbufferSwap=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
constbuf=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
constbuf=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+
constbuf=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-
constswap16n=binding.swap16;
28-
constswap32n=binding.swap32;
29-
30-
functionswap(b,n,m){
31-
consti=b[n];
32-
b[n]=b[m];
33-
b[m]=i;
34-
}
35-
36-
Buffer.prototype.swap16=functionswap16(){
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-
constlen=this.length;
41-
if(len%2!==0)
42-
thrownewRangeError('Buffer size must be a multiple of 16-bits');
43-
if(len<512){
44-
for(vari=0;i<len;i+=2)
45-
swap(this,i,i+1);
46-
returnthis;
47-
}
48-
returnswap16n.apply(this);
49-
};
50-
51-
Buffer.prototype.swap32=functionswap32(){
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-
constlen=this.length;
56-
if(len%4!==0)
57-
thrownewRangeError('Buffer size must be a multiple of 32-bits');
58-
if(len<1024){
59-
for(vari=0;i<len;i+=4){
60-
swap(this,i,i+3);
61-
swap(this,i+1,i+2);
62-
}
63-
returnthis;
64-
}
65-
returnswap32n.apply(this);
66-
};
67-
6827
constflags=bindingObj.flags;
6928
constkNoZeroFill=0;
7029

@@ -1320,3 +1279,67 @@ Buffer.prototype.writeDoubleBE = function writeDoubleBE(val, offset, noAssert) {
13201279
binding.writeDoubleBE(this,val,offset,true);
13211280
returnoffset+8;
13221281
};
1282+
1283+
constswap16n=binding.swap16;
1284+
constswap32n=binding.swap32;
1285+
constswap64n=binding.swap64;
1286+
1287+
functionswap(b,n,m){
1288+
consti=b[n];
1289+
b[n]=b[m];
1290+
b[m]=i;
1291+
}
1292+
1293+
1294+
Buffer.prototype.swap16=functionswap16(){
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+
constlen=this.length;
1299+
if(len%2!==0)
1300+
thrownewRangeError('Buffer size must be a multiple of 16-bits');
1301+
if(len<128){
1302+
for(vari=0;i<len;i+=2)
1303+
swap(this,i,i+1);
1304+
returnthis;
1305+
}
1306+
returnswap16n(this);
1307+
};
1308+
1309+
1310+
Buffer.prototype.swap32=functionswap32(){
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+
constlen=this.length;
1315+
if(len%4!==0)
1316+
thrownewRangeError('Buffer size must be a multiple of 32-bits');
1317+
if(len<192){
1318+
for(vari=0;i<len;i+=4){
1319+
swap(this,i,i+3);
1320+
swap(this,i+1,i+2);
1321+
}
1322+
returnthis;
1323+
}
1324+
returnswap32n(this);
1325+
};
1326+
1327+
1328+
Buffer.prototype.swap64=functionswap64(){
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+
constlen=this.length;
1333+
if(len%8!==0)
1334+
thrownewRangeError('Buffer size must be a multiple of 64-bits');
1335+
if(len<192){
1336+
for(vari=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+
returnthis;
1343+
}
1344+
returnswap64n(this);
1345+
};

0 commit comments

Comments
 (0)