Skip to content

Commit df30fd5

Browse files
bnoordhuisaddaleax
authored andcommitted
buffer: optimize readDouble and readFloat methods
Compute the floating point number in JavaScript to avoid having to call out to the C++ runtime. The improvements are not insubstantial: improvement confidence p.value value="big" endian="BE" type="Double" noAssert="false" 292.86 % *** 1.688367e-08 value="big" endian="BE" type="Double" noAssert="true" 353.19 % *** 6.079414e-10 value="big" endian="BE" type="Float" noAssert="false" 406.21 % *** 1.730122e-07 value="big" endian="BE" type="Float" noAssert="true" 450.81 % *** 6.909242e-07 value="big" endian="LE" type="Double" noAssert="false" 268.39 % *** 8.625486e-09 value="big" endian="LE" type="Double" noAssert="true" 310.66 % *** 2.798332e-15 value="big" endian="LE" type="Float" noAssert="false" 382.99 % *** 3.412057e-07 value="big" endian="LE" type="Float" noAssert="true" 394.60 % *** 1.406742e-07 value="inf" endian="BE" type="Double" noAssert="false" 312.91 % *** 7.407943e-12 value="inf" endian="BE" type="Double" noAssert="true" 392.47 % *** 3.821179e-08 value="inf" endian="BE" type="Float" noAssert="false" 466.01 % *** 8.953363e-08 value="inf" endian="BE" type="Float" noAssert="true" 460.76 % *** 5.381256e-09 value="inf" endian="LE" type="Double" noAssert="false" 279.50 % *** 2.390682e-09 value="inf" endian="LE" type="Double" noAssert="true" 335.30 % *** 3.587173e-09 value="inf" endian="LE" type="Float" noAssert="false" 439.77 % *** 1.057133e-07 value="inf" endian="LE" type="Float" noAssert="true" 426.72 % *** 4.353408e-09 value="nan" endian="BE" type="Double" noAssert="false" 271.18 % *** 2.281526e-05 value="nan" endian="BE" type="Double" noAssert="true" 312.63 % *** 1.974975e-07 value="nan" endian="BE" type="Float" noAssert="false" 429.17 % *** 2.416228e-07 value="nan" endian="BE" type="Float" noAssert="true" 461.39 % *** 1.956714e-08 value="nan" endian="LE" type="Double" noAssert="false" 267.03 % *** 9.938479e-12 value="nan" endian="LE" type="Double" noAssert="true" 276.93 % *** 7.842481e-08 value="nan" endian="LE" type="Float" noAssert="false" 415.97 % *** 8.082710e-07 value="nan" endian="LE" type="Float" noAssert="true" 433.68 % *** 1.030200e-07 value="small" endian="BE" type="Double" noAssert="false" 273.20 % *** 9.071652e-11 value="small" endian="BE" type="Double" noAssert="true" 326.25 % *** 3.120167e-08 value="small" endian="BE" type="Float" noAssert="false" 845.61 % *** 8.044170e-08 value="small" endian="BE" type="Float" noAssert="true" 868.61 % *** 2.944539e-08 value="small" endian="LE" type="Double" noAssert="false" 251.29 % *** 5.613930e-09 value="small" endian="LE" type="Double" noAssert="true" 286.82 % *** 8.149603e-10 value="small" endian="LE" type="Float" noAssert="false" 824.87 % *** 1.199729e-08 value="small" endian="LE" type="Float" noAssert="true" 834.35 % *** 4.799620e-08 value="zero" endian="BE" type="Double" noAssert="false" 216.70 % *** 3.872293e-12 value="zero" endian="BE" type="Double" noAssert="true" 239.31 % *** 6.439601e-09 value="zero" endian="BE" type="Float" noAssert="false" 353.75 % *** 3.639974e-07 value="zero" endian="BE" type="Float" noAssert="true" 388.86 % *** 7.074318e-10 value="zero" endian="LE" type="Double" noAssert="false" 179.34 % *** 5.230763e-06 value="zero" endian="LE" type="Double" noAssert="true" 199.66 % *** 2.177589e-11 value="zero" endian="LE" type="Float" noAssert="false" 299.55 % *** 9.961978e-08 value="zero" endian="LE" type="Float" noAssert="true" 333.30 % *** 2.470764e-08 PR-URL: nodejs#17775 Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net>
1 parent cb3de88 commit df30fd5

File tree

3 files changed

+111
-72
lines changed

3 files changed

+111
-72
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
'use strict';
2+
const common = require('../common.js');
3+
4+
const bench = common.createBenchmark(main, {
5+
noAssert: ['false', 'true'],
6+
type: ['Double', 'Float'],
7+
endian: ['BE', 'LE'],
8+
value: ['zero', 'big', 'small', 'inf', 'nan'],
9+
millions: [1]
10+
});
11+
12+
function main(conf) {
13+
const noAssert = conf.noAssert === 'true';
14+
const len = +conf.millions * 1e6;
15+
const buff = Buffer.alloc(8);
16+
const type = conf.type || 'Double';
17+
const endian = conf.endian;
18+
const fn = `read${type}${endian}`;
19+
const values = {
20+
Double: {
21+
zero: 0,
22+
big: 2 ** 1023,
23+
small: 2 ** -1074,
24+
inf: Infinity,
25+
nan: NaN,
26+
},
27+
Float: {
28+
zero: 0,
29+
big: 2 ** 127,
30+
small: 2 ** -149,
31+
inf: Infinity,
32+
nan: NaN,
33+
},
34+
};
35+
const value = values[type][conf.value];
36+
37+
buff[`write${type}${endian}`](value, 0, noAssert);
38+
const testFunction = new Function('buff', `
39+
for (var i = 0; i !== ${len}; i++) {
40+
buff.${fn}(0, ${JSON.stringify(noAssert)});
41+
}
42+
`);
43+
bench.start();
44+
testFunction(buff);
45+
bench.end(len / 1e6);
46+
}

lib/buffer.js

Lines changed: 65 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,6 @@ const {
3131
indexOfBuffer,
3232
indexOfNumber,
3333
indexOfString,
34-
readDoubleBE: _readDoubleBE,
35-
readDoubleLE: _readDoubleLE,
36-
readFloatBE: _readFloatBE,
37-
readFloatLE: _readFloatLE,
3834
swap16: _swap16,
3935
swap32: _swap32,
4036
swap64: _swap64,
@@ -1201,35 +1197,80 @@ Buffer.prototype.readInt32BE = function readInt32BE(offset, noAssert) {
12011197
};
12021198

12031199

1204-
Buffer.prototype.readFloatLE = function readFloatLE(offset, noAssert) {
1205-
offset = offset >>> 0;
1206-
if (!noAssert)
1207-
checkOffset(offset, 4, this.length);
1208-
return _readFloatLE(this, offset);
1200+
// For the casual reader who has not at the current time memorized the
1201+
// IEEE-754 standard in full detail: floating point numbers consist of
1202+
// a fraction, an exponent and a sign bit: 23+8+1 bits for single precision
1203+
// numbers and 52+11+1 bits for double precision numbers.
1204+
//
1205+
// A zero exponent is either a positive or negative zero, if the fraction
1206+
// is zero, or a denormalized number when it is non-zero. Multiplying the
1207+
// fraction by the smallest possible denormal yields the denormalized number.
1208+
//
1209+
// An all-bits-one exponent is either a positive or negative infinity, if
1210+
// the fraction is zero, or NaN when it is non-zero. The standard allows
1211+
// both quiet and signalling NaNs but since NaN is a canonical value in
1212+
// JavaScript, we cannot (and do not) distinguish between the two.
1213+
//
1214+
// Other exponents are regular numbers and are computed by subtracting the bias
1215+
// from the exponent (127 for single precision, 1023 for double precision),
1216+
// yielding an exponent in the ranges -126-127 and -1022-1024 respectively.
1217+
//
1218+
// Of interest is that the fraction of a normal number has an extra bit of
1219+
// precision that is not stored but is reconstructed by adding one after
1220+
// multiplying the fraction with the result of 2**-bits_in_fraction.
1221+
1222+
1223+
function toDouble(x0, x1) {
1224+
const frac = x0 + 0x100000000 * (x1 & 0xFFFFF);
1225+
const expt = (x1 >>> 20) & 2047;
1226+
const sign = (x1 >>> 31) ? -1 : 1;
1227+
if (expt === 0) {
1228+
if (frac === 0) return sign * 0;
1229+
return sign * frac * 2 ** -1074;
1230+
} else if (expt === 2047) {
1231+
if (frac === 0) return sign * Infinity;
1232+
return NaN;
1233+
}
1234+
return sign * 2 ** (expt - 1023) * (1 + frac * 2 ** -52);
1235+
}
1236+
1237+
1238+
function toFloat(x) {
1239+
const frac = x & 0x7FFFFF;
1240+
const expt = (x >>> 23) & 255;
1241+
const sign = (x >>> 31) ? -1 : 1;
1242+
if (expt === 0) {
1243+
if (frac === 0) return sign * 0;
1244+
return sign * frac * 2 ** -149;
1245+
} else if (expt === 255) {
1246+
if (frac === 0) return sign * Infinity;
1247+
return NaN;
1248+
}
1249+
return sign * 2 ** (expt - 127) * (1 + frac * 2 ** -23);
1250+
}
1251+
1252+
1253+
Buffer.prototype.readDoubleBE = function(offset, noAssert) {
1254+
const x1 = this.readUInt32BE(offset + 0, noAssert);
1255+
const x0 = this.readUInt32BE(offset + 4, noAssert);
1256+
return toDouble(x0, x1);
12091257
};
12101258

12111259

1212-
Buffer.prototype.readFloatBE = function readFloatBE(offset, noAssert) {
1213-
offset = offset >>> 0;
1214-
if (!noAssert)
1215-
checkOffset(offset, 4, this.length);
1216-
return _readFloatBE(this, offset);
1260+
Buffer.prototype.readDoubleLE = function(offset, noAssert) {
1261+
const x0 = this.readUInt32LE(offset + 0, noAssert);
1262+
const x1 = this.readUInt32LE(offset + 4, noAssert);
1263+
return toDouble(x0, x1);
12171264
};
12181265

12191266

1220-
Buffer.prototype.readDoubleLE = function readDoubleLE(offset, noAssert) {
1221-
offset = offset >>> 0;
1222-
if (!noAssert)
1223-
checkOffset(offset, 8, this.length);
1224-
return _readDoubleLE(this, offset);
1267+
Buffer.prototype.readFloatBE = function(offset, noAssert) {
1268+
return toFloat(this.readUInt32BE(offset, noAssert));
12251269
};
12261270

12271271

1228-
Buffer.prototype.readDoubleBE = function readDoubleBE(offset, noAssert) {
1229-
offset = offset >>> 0;
1230-
if (!noAssert)
1231-
checkOffset(offset, 8, this.length);
1232-
return _readDoubleBE(this, offset);
1272+
Buffer.prototype.readFloatLE = function(offset, noAssert) {
1273+
return toFloat(this.readUInt32LE(offset, noAssert));
12331274
};
12341275

12351276

src/node_buffer.cc

Lines changed: 0 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -719,49 +719,6 @@ static inline void Swizzle(char* start, unsigned int len) {
719719
}
720720

721721

722-
template <typename T, enum Endianness endianness>
723-
void ReadFloatGeneric(const FunctionCallbackInfo<Value>& args) {
724-
THROW_AND_RETURN_UNLESS_BUFFER(Environment::GetCurrent(args), args[0]);
725-
SPREAD_BUFFER_ARG(args[0], ts_obj);
726-
727-
uint32_t offset = args[1]->Uint32Value();
728-
CHECK_LE(offset + sizeof(T), ts_obj_length);
729-
730-
union NoAlias {
731-
T val;
732-
char bytes[sizeof(T)];
733-
};
734-
735-
union NoAlias na;
736-
const char* ptr = static_cast<const char*>(ts_obj_data) + offset;
737-
memcpy(na.bytes, ptr, sizeof(na.bytes));
738-
if (endianness != GetEndianness())
739-
Swizzle(na.bytes, sizeof(na.bytes));
740-
741-
args.GetReturnValue().Set(na.val);
742-
}
743-
744-
745-
void ReadFloatLE(const FunctionCallbackInfo<Value>& args) {
746-
ReadFloatGeneric<float, kLittleEndian>(args);
747-
}
748-
749-
750-
void ReadFloatBE(const FunctionCallbackInfo<Value>& args) {
751-
ReadFloatGeneric<float, kBigEndian>(args);
752-
}
753-
754-
755-
void ReadDoubleLE(const FunctionCallbackInfo<Value>& args) {
756-
ReadFloatGeneric<double, kLittleEndian>(args);
757-
}
758-
759-
760-
void ReadDoubleBE(const FunctionCallbackInfo<Value>& args) {
761-
ReadFloatGeneric<double, kBigEndian>(args);
762-
}
763-
764-
765722
template <typename T, enum Endianness endianness>
766723
void WriteFloatGeneric(const FunctionCallbackInfo<Value>& args) {
767724
Environment* env = Environment::GetCurrent(args);
@@ -1276,11 +1233,6 @@ void Initialize(Local<Object> target,
12761233
env->SetMethod(target, "indexOfNumber", IndexOfNumber);
12771234
env->SetMethod(target, "indexOfString", IndexOfString);
12781235

1279-
env->SetMethod(target, "readDoubleBE", ReadDoubleBE);
1280-
env->SetMethod(target, "readDoubleLE", ReadDoubleLE);
1281-
env->SetMethod(target, "readFloatBE", ReadFloatBE);
1282-
env->SetMethod(target, "readFloatLE", ReadFloatLE);
1283-
12841236
env->SetMethod(target, "writeDoubleBE", WriteDoubleBE);
12851237
env->SetMethod(target, "writeDoubleLE", WriteDoubleLE);
12861238
env->SetMethod(target, "writeFloatBE", WriteFloatBE);

0 commit comments

Comments
 (0)