plain js utf8 is faster for short strings

This commit is contained in:
dcodeIO 2016-12-03 13:35:20 +01:00
parent 0ae6675236
commit 98d6ae186a
12 changed files with 167 additions and 123 deletions

View File

@ -372,33 +372,33 @@ The package includes a [benchmark](https://github.com/dcodeIO/protobuf.js/tree/m
```
benchmarking encoding performance ...
Type.encode to buffer x 402,572 ops/sec ±1.09% (90 runs sampled)
JSON.stringify to string x 342,004 ops/sec ±1.46% (82 runs sampled)
JSON.stringify to buffer x 184,468 ops/sec ±1.76% (79 runs sampled)
Type.encode to buffer x 514,048 ops/sec ±0.75% (93 runs sampled)
JSON.stringify to string x 355,935 ops/sec ±0.79% (91 runs sampled)
JSON.stringify to buffer x 191,023 ops/sec ±1.39% (86 runs sampled)
Type.encode to buffer was fastest
JSON.stringify to string was 15.4% slower
JSON.stringify to buffer was 54.5% slower
JSON.stringify to string was 30.8% slower
JSON.stringify to buffer was 63.1% slower
benchmarking decoding performance ...
Type.decode from buffer x 1,170,490 ops/sec ±1.49% (88 runs sampled)
JSON.parse from string x 328,975 ops/sec ±0.90% (88 runs sampled)
JSON.parse from buffer x 298,702 ops/sec ±0.82% (89 runs sampled)
Type.decode from buffer x 1,238,587 ops/sec ±1.73% (87 runs sampled)
JSON.parse from string x 312,168 ops/sec ±2.22% (83 runs sampled)
JSON.parse from buffer x 272,975 ops/sec ±2.45% (82 runs sampled)
Type.decode from buffer was fastest
JSON.parse from string was 71.7% slower
JSON.parse from buffer was 74.3% slower
JSON.parse from string was 74.9% slower
JSON.parse from buffer was 78.1% slower
benchmarking combined performance ...
Type to/from buffer x 218,688 ops/sec ±1.49% (90 runs sampled)
JSON to/from string x 144,634 ops/sec ±1.97% (87 runs sampled)
JSON to/from buffer x 102,350 ops/sec ±1.23% (92 runs sampled)
Type to/from buffer x 246,428 ops/sec ±1.52% (89 runs sampled)
JSON to/from string x 136,380 ops/sec ±1.50% (80 runs sampled)
JSON to/from buffer x 95,229 ops/sec ±1.93% (86 runs sampled)
Type to/from buffer was fastest
JSON to/from string was 34.2% slower
JSON to/from buffer was 53.1% slower
JSON to/from string was 44.6% slower
JSON to/from buffer was 61.5% slower
```
Note that JSON is a native binding nowadays and as such is *really* fast. So, how can protobuf.js be faster?

View File

@ -1,8 +1,5 @@
var benchmark = require("benchmark"),
chalk = require("chalk");
var protobuf = require("../src/index"),
suite = new benchmark.Suite(),
newSuite = require("./suite"),
data = require("./bench.json");
// NOTE: This benchmark is flawed in that it compares protocol buffers, which is purely a binary
@ -68,47 +65,4 @@ protobuf.load(require.resolve("./bench.proto"), function onload(err, root) {
})
.run();
});
var padSize = 27;
function newSuite(name) {
var benches = [];
return new benchmark.Suite(name)
.on("add", function(event) {
benches.push(event.target);
})
.on("start", function() {
console.log("benchmarking " + name + " performance ...\n");
})
.on("error", function(err) {
console.log("ERROR:", err);
})
.on("cycle", function(event) {
console.log(String(event.target));
})
.on("complete", function(event) {
var fastest = this.filter('fastest'),
slowest = this.filter('slowest');
var fastestHz = getHz(fastest[0]);
console.log("\n" + chalk.white(pad(fastest[0].name, padSize)) + " was " + chalk.green("fastest"));
benches.forEach(function(bench) {
if (fastest.indexOf(bench) > -1)
return;
var hz = hz = getHz(bench);
var percent = (1 - (hz / fastestHz)) * 100;
console.log(chalk.white(pad(bench.name, padSize)) + " was " + chalk.red(percent.toFixed(1)+'% slower'));
});
console.log();
});
}
function getHz(bench) {
return 1 / (bench.stats.mean + bench.stats.moe);
}
function pad(str, len, l) {
while (str.length < len)
str = l ? str + " " : " " + str;
return str;
}
});

45
bench/suite.js Normal file
View File

@ -0,0 +1,45 @@
var benchmark = require("benchmark"),
chalk = require("chalk");
var padSize = 27;
module.exports = function newSuite(name) {
var benches = [];
return new benchmark.Suite(name)
.on("add", function(event) {
benches.push(event.target);
})
.on("start", function() {
console.log("benchmarking " + name + " performance ...\n");
})
.on("error", function(err) {
console.log("ERROR:", err);
})
.on("cycle", function(event) {
console.log(String(event.target));
})
.on("complete", function(event) {
var fastest = this.filter('fastest'),
slowest = this.filter('slowest');
var fastestHz = getHz(fastest[0]);
console.log("\n" + chalk.white(pad(fastest[0].name, padSize)) + " was " + chalk.green("fastest"));
benches.forEach(function(bench) {
if (fastest.indexOf(bench) > -1)
return;
var hz = hz = getHz(bench);
var percent = (1 - (hz / fastestHz)) * 100;
console.log(chalk.white(pad(bench.name, padSize)) + " was " + chalk.red(percent.toFixed(1)+'% slower'));
});
console.log();
});
}
function getHz(bench) {
return 1 / (bench.stats.mean + bench.stats.moe);
}
function pad(str, len, l) {
while (str.length < len)
str = l ? str + " " : " " + str;
return str;
}

45
bench/write.js Normal file
View File

@ -0,0 +1,45 @@
var protobuf = require("../src/index"),
newSuite = require("./suite");
newSuite("float")
.add("Writer#float", function() {
var writer = new protobuf.Writer();
writer.float(0.1);
writer.finish();
})
.add("BufferWriter#float", function() {
var writer = new protobuf.BufferWriter();
writer.float(0.1);
writer.finish();
})
.run();
newSuite("double")
.add("Writer#double", function() {
var writer = new protobuf.Writer();
writer.double(0.1);
writer.finish();
})
.add("BufferWriter#double", function() {
var writer = new protobuf.BufferWriter();
writer.double(0.1);
writer.finish();
})
.run();
var bytes = [0, 0, 0, 0, 0, 0, 0, 0];
var arrayBytes = new Uint8Array(bytes);
var bufferBytes = Buffer.from(bytes);
newSuite("bytes")
.add("Writer#bytes", function() {
var writer = new protobuf.Writer();
writer.bytes(arrayBytes);
writer.finish();
})
.add("BufferWriter#bytes", function() {
var writer = new protobuf.BufferWriter();
writer.bytes(bufferBytes);
writer.finish();
})
.run();

56
dist/protobuf.js vendored
View File

@ -1,6 +1,6 @@
/*!
* protobuf.js v6.0.1 (c) 2016 Daniel Wirtz
* Compiled Sat, 03 Dec 2016 11:37:47 UTC
* Compiled Sat, 03 Dec 2016 12:34:45 UTC
* Licensed under the Apache License, Version 2.0
* see: https://github.com/dcodeIO/protobuf.js for details
*/
@ -5498,9 +5498,9 @@ function writeString(buf, pos, val) {
if (c1 < 128) {
buf[pos++] = c1;
} else if (c1 < 2048) {
buf[pos++] = c1 >> 6 | 192;
buf[pos++] = c1 & 63 | 128;
} else if ((c1 & 0xFC00) === 0xD800 && i + 1 < val.length && ((c2 = val.charCodeAt(i + 1)) & 0xFC00) === 0xDC00) {
buf[pos++] = c1 >> 6 | 192;
buf[pos++] = c1 & 63 | 128;
} else if ((c1 & 0xFC00) === 0xD800 && ((c2 = val.charCodeAt(i + 1)) & 0xFC00) === 0xDC00) {
c1 = 0x10000 + ((c1 & 0x03FF) << 10) + (c2 & 0x03FF);
++i;
buf[pos++] = c1 >> 18 | 240;
@ -5517,23 +5517,20 @@ function writeString(buf, pos, val) {
function byteLength(val) {
var strlen = val.length >>> 0;
if (strlen) {
var len = 0;
for (var i = 0, c1; i < strlen; ++i) {
c1 = val.charCodeAt(i);
if (c1 < 128)
len += 1;
else if (c1 < 2048)
len += 2;
else if ((c1 & 0xFC00) === 0xD800 && i + 1 < strlen && (val.charCodeAt(i + 1) & 0xFC00) === 0xDC00) {
++i;
len += 4;
} else
len += 3;
}
return len;
var len = 0;
for (var i = 0; i < strlen; ++i) {
var c1 = val.charCodeAt(i);
if (c1 < 128)
len += 1;
else if (c1 < 2048)
len += 2;
else if ((c1 & 0xFC00) === 0xD800 && (val.charCodeAt(i + 1) & 0xFC00) === 0xDC00) {
++i;
len += 4;
} else
len += 3;
}
return 0;
return len;
}
/**
@ -5666,17 +5663,22 @@ BufferWriterPrototype.bytes = function write_bytes_buffer(value) {
};
function writeStringBuffer(buf, pos, val) {
buf.write(val, pos);
if (val.length < 40) // plain js is faster for short strings
writeString(buf, pos, val);
else
buf.write(val, pos);
}
/**
* @override
*/
BufferWriterPrototype.string = function write_string_buffer(value) {
var len = byteLength(value);
BufferWriterPrototype.string = function write_string_buffer(value) {
var len = value.length < 40
? byteLength(value)
: util.Buffer.byteLength(value);
return len
? this.uint32(len).push(writeStringBuffer, len, value)
: this.push(writeByte, 1, 0);
? this.uint32(len).push(writeStringBuffer, len, value)
: this.push(writeByte, 1, 0);
};
/**
@ -5702,8 +5704,6 @@ configure();
"use strict";
var protobuf = global.protobuf = exports;
var util = require(21);
/**
* Loads one or multiple .proto or preprocessed .json files into a common root namespace.
* @param {string|string[]} filename One or multiple files to load
@ -5755,7 +5755,7 @@ protobuf.inherits = require(7);
// Utility
protobuf.types = require(20);
protobuf.common = require(2);
protobuf.util = util;
protobuf.util = require(21);
// Be nice to AMD
/* eslint-disable no-undef */

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -1,8 +1,6 @@
"use strict";
var protobuf = global.protobuf = exports;
var util = require("./util");
/**
* Loads one or multiple .proto or preprocessed .json files into a common root namespace.
* @param {string|string[]} filename One or multiple files to load
@ -54,7 +52,7 @@ protobuf.inherits = require("./inherits");
// Utility
protobuf.types = require("./types");
protobuf.common = require("./common");
protobuf.util = util;
protobuf.util = require("./util");
// Be nice to AMD
/* eslint-disable no-undef */

View File

@ -367,9 +367,9 @@ function writeString(buf, pos, val) {
if (c1 < 128) {
buf[pos++] = c1;
} else if (c1 < 2048) {
buf[pos++] = c1 >> 6 | 192;
buf[pos++] = c1 & 63 | 128;
} else if ((c1 & 0xFC00) === 0xD800 && i + 1 < val.length && ((c2 = val.charCodeAt(i + 1)) & 0xFC00) === 0xDC00) {
buf[pos++] = c1 >> 6 | 192;
buf[pos++] = c1 & 63 | 128;
} else if ((c1 & 0xFC00) === 0xD800 && ((c2 = val.charCodeAt(i + 1)) & 0xFC00) === 0xDC00) {
c1 = 0x10000 + ((c1 & 0x03FF) << 10) + (c2 & 0x03FF);
++i;
buf[pos++] = c1 >> 18 | 240;
@ -386,23 +386,20 @@ function writeString(buf, pos, val) {
function byteLength(val) {
var strlen = val.length >>> 0;
if (strlen) {
var len = 0;
for (var i = 0, c1; i < strlen; ++i) {
c1 = val.charCodeAt(i);
if (c1 < 128)
len += 1;
else if (c1 < 2048)
len += 2;
else if ((c1 & 0xFC00) === 0xD800 && i + 1 < strlen && (val.charCodeAt(i + 1) & 0xFC00) === 0xDC00) {
++i;
len += 4;
} else
len += 3;
}
return len;
var len = 0;
for (var i = 0; i < strlen; ++i) {
var c1 = val.charCodeAt(i);
if (c1 < 128)
len += 1;
else if (c1 < 2048)
len += 2;
else if ((c1 & 0xFC00) === 0xD800 && (val.charCodeAt(i + 1) & 0xFC00) === 0xDC00) {
++i;
len += 4;
} else
len += 3;
}
return 0;
return len;
}
/**
@ -535,17 +532,22 @@ BufferWriterPrototype.bytes = function write_bytes_buffer(value) {
};
function writeStringBuffer(buf, pos, val) {
buf.write(val, pos);
if (val.length < 40) // plain js is faster for short strings
writeString(buf, pos, val);
else
buf.write(val, pos);
}
/**
* @override
*/
BufferWriterPrototype.string = function write_string_buffer(value) {
var len = byteLength(value);
BufferWriterPrototype.string = function write_string_buffer(value) {
var len = value.length < 40
? byteLength(value)
: util.Buffer.byteLength(value);
return len
? this.uint32(len).push(writeStringBuffer, len, value)
: this.push(writeByte, 1, 0);
? this.uint32(len).push(writeStringBuffer, len, value)
: this.push(writeByte, 1, 0);
};
/**

View File

@ -3,7 +3,7 @@
/*
* protobuf.js v6.0.1 TypeScript definitions
* Generated Sat, 03 Dec 2016 11:38:06 UTC
* Generated Sat, 03 Dec 2016 12:34:52 UTC
*/
declare module "protobufjs" {