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: https://github.com/nodejs/node/pull/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>
This commit is contained in:
Ben Noordhuis 2017-12-20 01:15:22 +01:00 committed by Anna Henningsen
parent cb3de880be
commit df30fd586d
No known key found for this signature in database
GPG Key ID: 9C63F3A6CD2AD8F9
3 changed files with 111 additions and 72 deletions

View File

@ -0,0 +1,46 @@
'use strict';
const common = require('../common.js');
const bench = common.createBenchmark(main, {
noAssert: ['false', 'true'],
type: ['Double', 'Float'],
endian: ['BE', 'LE'],
value: ['zero', 'big', 'small', 'inf', 'nan'],
millions: [1]
});
function main(conf) {
const noAssert = conf.noAssert === 'true';
const len = +conf.millions * 1e6;
const buff = Buffer.alloc(8);
const type = conf.type || 'Double';
const endian = conf.endian;
const fn = `read${type}${endian}`;
const values = {
Double: {
zero: 0,
big: 2 ** 1023,
small: 2 ** -1074,
inf: Infinity,
nan: NaN,
},
Float: {
zero: 0,
big: 2 ** 127,
small: 2 ** -149,
inf: Infinity,
nan: NaN,
},
};
const value = values[type][conf.value];
buff[`write${type}${endian}`](value, 0, noAssert);
const testFunction = new Function('buff', `
for (var i = 0; i !== ${len}; i++) {
buff.${fn}(0, ${JSON.stringify(noAssert)});
}
`);
bench.start();
testFunction(buff);
bench.end(len / 1e6);
}

View File

@ -31,10 +31,6 @@ const {
indexOfBuffer,
indexOfNumber,
indexOfString,
readDoubleBE: _readDoubleBE,
readDoubleLE: _readDoubleLE,
readFloatBE: _readFloatBE,
readFloatLE: _readFloatLE,
swap16: _swap16,
swap32: _swap32,
swap64: _swap64,
@ -1201,35 +1197,80 @@ Buffer.prototype.readInt32BE = function readInt32BE(offset, noAssert) {
};
Buffer.prototype.readFloatLE = function readFloatLE(offset, noAssert) {
offset = offset >>> 0;
if (!noAssert)
checkOffset(offset, 4, this.length);
return _readFloatLE(this, offset);
// For the casual reader who has not at the current time memorized the
// IEEE-754 standard in full detail: floating point numbers consist of
// a fraction, an exponent and a sign bit: 23+8+1 bits for single precision
// numbers and 52+11+1 bits for double precision numbers.
//
// A zero exponent is either a positive or negative zero, if the fraction
// is zero, or a denormalized number when it is non-zero. Multiplying the
// fraction by the smallest possible denormal yields the denormalized number.
//
// An all-bits-one exponent is either a positive or negative infinity, if
// the fraction is zero, or NaN when it is non-zero. The standard allows
// both quiet and signalling NaNs but since NaN is a canonical value in
// JavaScript, we cannot (and do not) distinguish between the two.
//
// Other exponents are regular numbers and are computed by subtracting the bias
// from the exponent (127 for single precision, 1023 for double precision),
// yielding an exponent in the ranges -126-127 and -1022-1024 respectively.
//
// Of interest is that the fraction of a normal number has an extra bit of
// precision that is not stored but is reconstructed by adding one after
// multiplying the fraction with the result of 2**-bits_in_fraction.
function toDouble(x0, x1) {
const frac = x0 + 0x100000000 * (x1 & 0xFFFFF);
const expt = (x1 >>> 20) & 2047;
const sign = (x1 >>> 31) ? -1 : 1;
if (expt === 0) {
if (frac === 0) return sign * 0;
return sign * frac * 2 ** -1074;
} else if (expt === 2047) {
if (frac === 0) return sign * Infinity;
return NaN;
}
return sign * 2 ** (expt - 1023) * (1 + frac * 2 ** -52);
}
function toFloat(x) {
const frac = x & 0x7FFFFF;
const expt = (x >>> 23) & 255;
const sign = (x >>> 31) ? -1 : 1;
if (expt === 0) {
if (frac === 0) return sign * 0;
return sign * frac * 2 ** -149;
} else if (expt === 255) {
if (frac === 0) return sign * Infinity;
return NaN;
}
return sign * 2 ** (expt - 127) * (1 + frac * 2 ** -23);
}
Buffer.prototype.readDoubleBE = function(offset, noAssert) {
const x1 = this.readUInt32BE(offset + 0, noAssert);
const x0 = this.readUInt32BE(offset + 4, noAssert);
return toDouble(x0, x1);
};
Buffer.prototype.readFloatBE = function readFloatBE(offset, noAssert) {
offset = offset >>> 0;
if (!noAssert)
checkOffset(offset, 4, this.length);
return _readFloatBE(this, offset);
Buffer.prototype.readDoubleLE = function(offset, noAssert) {
const x0 = this.readUInt32LE(offset + 0, noAssert);
const x1 = this.readUInt32LE(offset + 4, noAssert);
return toDouble(x0, x1);
};
Buffer.prototype.readDoubleLE = function readDoubleLE(offset, noAssert) {
offset = offset >>> 0;
if (!noAssert)
checkOffset(offset, 8, this.length);
return _readDoubleLE(this, offset);
Buffer.prototype.readFloatBE = function(offset, noAssert) {
return toFloat(this.readUInt32BE(offset, noAssert));
};
Buffer.prototype.readDoubleBE = function readDoubleBE(offset, noAssert) {
offset = offset >>> 0;
if (!noAssert)
checkOffset(offset, 8, this.length);
return _readDoubleBE(this, offset);
Buffer.prototype.readFloatLE = function(offset, noAssert) {
return toFloat(this.readUInt32LE(offset, noAssert));
};

View File

@ -719,49 +719,6 @@ static inline void Swizzle(char* start, unsigned int len) {
}
template <typename T, enum Endianness endianness>
void ReadFloatGeneric(const FunctionCallbackInfo<Value>& args) {
THROW_AND_RETURN_UNLESS_BUFFER(Environment::GetCurrent(args), args[0]);
SPREAD_BUFFER_ARG(args[0], ts_obj);
uint32_t offset = args[1]->Uint32Value();
CHECK_LE(offset + sizeof(T), ts_obj_length);
union NoAlias {
T val;
char bytes[sizeof(T)];
};
union NoAlias na;
const char* ptr = static_cast<const char*>(ts_obj_data) + offset;
memcpy(na.bytes, ptr, sizeof(na.bytes));
if (endianness != GetEndianness())
Swizzle(na.bytes, sizeof(na.bytes));
args.GetReturnValue().Set(na.val);
}
void ReadFloatLE(const FunctionCallbackInfo<Value>& args) {
ReadFloatGeneric<float, kLittleEndian>(args);
}
void ReadFloatBE(const FunctionCallbackInfo<Value>& args) {
ReadFloatGeneric<float, kBigEndian>(args);
}
void ReadDoubleLE(const FunctionCallbackInfo<Value>& args) {
ReadFloatGeneric<double, kLittleEndian>(args);
}
void ReadDoubleBE(const FunctionCallbackInfo<Value>& args) {
ReadFloatGeneric<double, kBigEndian>(args);
}
template <typename T, enum Endianness endianness>
void WriteFloatGeneric(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
@ -1276,11 +1233,6 @@ void Initialize(Local<Object> target,
env->SetMethod(target, "indexOfNumber", IndexOfNumber);
env->SetMethod(target, "indexOfString", IndexOfString);
env->SetMethod(target, "readDoubleBE", ReadDoubleBE);
env->SetMethod(target, "readDoubleLE", ReadDoubleLE);
env->SetMethod(target, "readFloatBE", ReadFloatBE);
env->SetMethod(target, "readFloatLE", ReadFloatLE);
env->SetMethod(target, "writeDoubleBE", WriteDoubleBE);
env->SetMethod(target, "writeDoubleLE", WriteDoubleLE);
env->SetMethod(target, "writeFloatBE", WriteFloatBE);