Skip to content

Commit

Permalink
buffer: optimize readDouble and readFloat methods
Browse files Browse the repository at this point in the history
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: #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>
  • Loading branch information
bnoordhuis authored and MylesBorins committed Jan 8, 2018
1 parent aca76db commit a203f0c
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 72 deletions.
46 changes: 46 additions & 0 deletions benchmark/buffers/buffer-read-float.js
Original file line number Diff line number Diff line change
@@ -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);
}
89 changes: 65 additions & 24 deletions lib/buffer.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,6 @@ const {
indexOfBuffer,
indexOfNumber,
indexOfString,
readDoubleBE: _readDoubleBE,
readDoubleLE: _readDoubleLE,
readFloatBE: _readFloatBE,
readFloatLE: _readFloatLE,
swap16: _swap16,
swap32: _swap32,
swap64: _swap64,
Expand Down Expand Up @@ -1197,35 +1193,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));
};


Expand Down
48 changes: 0 additions & 48 deletions src/node_buffer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -718,49 +718,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);
Expand Down Expand Up @@ -1275,11 +1232,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);
Expand Down

0 comments on commit a203f0c

Please sign in to comment.