From f8ad41a2d9c416d2aa4b1bbab6b0ec6cfab11742 Mon Sep 17 00:00:00 2001 From: "xiaochen.gaoxc" Date: Thu, 11 May 2017 02:39:25 +0800 Subject: [PATCH] refactor: enhance decode performance --- benchmark/decode.js | 311 ++++++++++++++++++++++---------------------- index.js | 7 +- lib/v1/decoder.js | 56 ++++---- lib/v2/decoder.js | 9 +- package.json | 16 +-- 5 files changed, 206 insertions(+), 193 deletions(-) diff --git a/benchmark/decode.js b/benchmark/decode.js index bb92f19..75b0807 100644 --- a/benchmark/decode.js +++ b/benchmark/decode.js @@ -70,163 +70,160 @@ var arrBuf2 = hessian.encode([1, 2, 3], '2.0'); var arrObjectBuf1 = hessian.encode([1, "name", "xxx1231231231231xxx123"], '1.0'); var arrObjectBuf2 = hessian.encode([1, "name", "xxx1231231231231xxx123"], '2.0'); -suite -.add('hessian1 decode: number', function() { - hessian.decode(number1Buf1, '1.0'); -}) -.add('hessian2 decode: number', function() { - hessian.decode(number1Buf2, '2.0'); -}) - -.add('hessian1 decode: date', function() { - hessian.decode(dateBuf1, '1.0'); -}) -.add('hessian2 decode: date', function() { - hessian.decode(dateBuf2, '2.0'); -}) - -.add('hessian1 decode: long', function() { - hessian.decode(longBuf1, '1.0'); -}) -.add('hessian2 decode: long', function() { - hessian.decode(longBuf2, '2.0'); -}) - -.add('hessian1 decode: string', function() { - hessian.decode(stringBuf1, '1.0'); -}) -.add('hessian2 decode: string', function() { - hessian.decode(stringBuf2, '2.0'); -}) - -.add('hessian1 decode: [1, 2, 3]', function() { - hessian.decode(arrBuf1, '1.0'); -}) -.add('hessian2 decode: [1, 2, 3]', function() { - hessian.decode(arrBuf2, '2.0'); -}) -.add('hessian1 decode array', function() { - hessian.decode(arrObjectBuf1, '1.0'); -}) -.add('hessian2 decode array', function() { - hessian.decode(arrObjectBuf2, '2.0'); -}) - -.add('hessian1 decode: simple object', function() { - hessian.decode(simpleObjectBuf1, '1.0'); -}) -.add('hessian2 decode: simple object', function() { - hessian.decode(simpleObjectBuf2, '2.0'); -}) - -.add('hessian1 decode: complex object', function() { - hessian.decode(complexObjectBuf1, '1.0'); -}) -.add('hessian2 decode: complex object', function() { - hessian.decode(complexObjectBuf2, '2.0'); -}) -.add('hessian1 decode with type: number', function() { - hessian.decode(number1Buf1, '1.0', true); -}) -.add('hessian2 decode with type: number', function() { - hessian.decode(number1Buf2, '2.0', true); -}) - -.add('hessian1 decode with type: date', function() { - hessian.decode(dateBuf1, '1.0', true); -}) -.add('hessian2 decode with type: date', function() { - hessian.decode(dateBuf2, '2.0', true); -}) - -.add('hessian1 decode with type: long', function() { - hessian.decode(longBuf1, '1.0', true); -}) -.add('hessian2 decode with type: long', function() { - hessian.decode(longBuf2, '2.0', true); -}) - -.add('hessian1 decode with type: string', function() { - hessian.decode(stringBuf1, '1.0', true); -}) -.add('hessian2 decode with type: string', function() { - hessian.decode(stringBuf2, '2.0', true); -}) - -.add('hessian1 decode with type: [1, 2, 3]', function() { - hessian.decode(arrBuf1, '1.0', true); -}) -.add('hessian2 decode with type: [1, 2, 3]', function() { - hessian.decode(arrBuf2, '2.0', true); -}) -.add('hessian1 decode with type array', function() { - hessian.decode(arrObjectBuf1, '1.0', true); -}) -.add('hessian2 decode with type array', function() { - hessian.decode(arrObjectBuf2, '2.0', true); -}) - -.add('hessian1 decode with type: simple object', function() { - hessian.decode(simpleObjectBuf1, '1.0', true); -}) -.add('hessian2 decode with type: simple object', function() { - hessian.decode(simpleObjectBuf2, '2.0', true); -}) - -.add('hessian1 decode with type: complex object', function() { - hessian.decode(complexObjectBuf1, '1.0', true); -}) -.add('hessian2 decode with type: complex object', function() { - hessian.decode(complexObjectBuf2, '2.0', true); -}) +suite + // .add('hessian1 decode: number', function() { + // hessian.decode(number1Buf1, '1.0'); + // }) + .add('hessian2 decode: number', function() { + hessian.decode(number1Buf2, '2.0'); + }) + // .add('hessian1 decode: date', function() { + // hessian.decode(dateBuf1, '1.0'); + // }) + .add('hessian2 decode: date', function() { + hessian.decode(dateBuf2, '2.0'); + }) + // .add('hessian1 decode: long', function() { + // hessian.decode(longBuf1, '1.0'); + // }) + // .add('hessian2 decode: long', function() { + // hessian.decode(longBuf2, '2.0'); + // }) + // .add('hessian1 decode: string', function() { + // hessian.decode(stringBuf1, '1.0'); + // }) + .add('hessian2 decode: string', function() { + hessian.decode(stringBuf2, '2.0'); + }) + +// .add('hessian1 decode: [1, 2, 3]', function() { +// hessian.decode(arrBuf1, '1.0'); +// }) +// .add('hessian2 decode: [1, 2, 3]', function() { +// hessian.decode(arrBuf2, '2.0'); +// }) +// .add('hessian1 decode array', function() { +// hessian.decode(arrObjectBuf1, '1.0'); +// }) +// .add('hessian2 decode array', function() { +// hessian.decode(arrObjectBuf2, '2.0'); +// }) + +// .add('hessian1 decode: simple object', function() { +// hessian.decode(simpleObjectBuf1, '1.0'); +// }) +// .add('hessian2 decode: simple object', function() { +// hessian.decode(simpleObjectBuf2, '2.0'); +// }) + +// .add('hessian1 decode: complex object', function() { +// hessian.decode(complexObjectBuf1, '1.0'); +// }) +// .add('hessian2 decode: complex object', function() { +// hessian.decode(complexObjectBuf2, '2.0'); +// }) +// .add('hessian1 decode with type: number', function() { +// hessian.decode(number1Buf1, '1.0', true); +// }) +// .add('hessian2 decode with type: number', function() { +// hessian.decode(number1Buf2, '2.0', true); +// }) + +// .add('hessian1 decode with type: date', function() { +// hessian.decode(dateBuf1, '1.0', true); +// }) +// .add('hessian2 decode with type: date', function() { +// hessian.decode(dateBuf2, '2.0', true); +// }) + +// .add('hessian1 decode with type: long', function() { +// hessian.decode(longBuf1, '1.0', true); +// }) +// .add('hessian2 decode with type: long', function() { +// hessian.decode(longBuf2, '2.0', true); +// }) + +// .add('hessian1 decode with type: string', function() { +// hessian.decode(stringBuf1, '1.0', true); +// }) +// .add('hessian2 decode with type: string', function() { +// hessian.decode(stringBuf2, '2.0', true); +// }) + +// .add('hessian1 decode with type: [1, 2, 3]', function() { +// hessian.decode(arrBuf1, '1.0', true); +// }) +// .add('hessian2 decode with type: [1, 2, 3]', function() { +// hessian.decode(arrBuf2, '2.0', true); +// }) +// .add('hessian1 decode with type array', function() { +// hessian.decode(arrObjectBuf1, '1.0', true); +// }) +// .add('hessian2 decode with type array', function() { +// hessian.decode(arrObjectBuf2, '2.0', true); +// }) + +// .add('hessian1 decode with type: simple object', function() { +// hessian.decode(simpleObjectBuf1, '1.0', true); +// }) +// .add('hessian2 decode with type: simple object', function() { +// hessian.decode(simpleObjectBuf2, '2.0', true); +// }) + +// .add('hessian1 decode with type: complex object', function() { +// hessian.decode(complexObjectBuf1, '1.0', true); +// }) +// .add('hessian2 decode with type: complex object', function() { +// hessian.decode(complexObjectBuf2, '2.0', true); +// }) .on('cycle', function(event) { - benchmarks.add(event.target); -}) -.on('start', function(event) { - console.log('\n Hessian Decode Benchmark\n node version: %s, date: %s\n Starting...', - process.version, Date()); -}) -.on('complete', function done() { - benchmarks.log(); -}) -.run({ 'async': false }); - - // Hessian Decode Benchmark - // node version: v0.11.12, date: Wed Jun 25 2014 10:46:26 GMT+0800 (CST) - // Starting... - // 32 tests completed. - - // hessian1 decode: number x 5,983,374 ops/sec ±1.57% (94 runs sampled) - // hessian2 decode: number x 5,713,562 ops/sec ±2.22% (91 runs sampled) - // hessian1 decode: date x 2,959,698 ops/sec ±1.19% (92 runs sampled) - // hessian2 decode: date x 2,548,345 ops/sec ±1.32% (91 runs sampled) - // hessian1 decode: long x 3,880,734 ops/sec ±1.66% (93 runs sampled) - // hessian2 decode: long x 5,221,659 ops/sec ±2.30% (90 runs sampled) - // hessian1 decode: string x 1,109,890 ops/sec ±1.59% (94 runs sampled) - // hessian2 decode: string x 1,075,246 ops/sec ±1.43% (92 runs sampled) - // hessian1 decode: [1, 2, 3] x 1,186,054 ops/sec ±1.24% (92 runs sampled) - // hessian2 decode: [1, 2, 3] x 2,049,867 ops/sec ±1.75% (98 runs sampled) - // hessian1 decode array x 511,250 ops/sec ±0.92% (95 runs sampled) - // hessian2 decode array x 600,564 ops/sec ±1.27% (90 runs sampled) - // hessian1 decode: simple object x 186,875 ops/sec ±0.81% (93 runs sampled) - // hessian2 decode: simple object x 174,768 ops/sec ±0.74% (94 runs sampled) - // hessian1 decode: complex object x 133,573 ops/sec ±1.31% (97 runs sampled) - // hessian2 decode: complex object x 131,234 ops/sec ±1.45% (95 runs sampled) - // hessian1 decode with type: number x 5,014,602 ops/sec ±1.97% (91 runs sampled) - // hessian2 decode with type: number x 5,890,098 ops/sec ±1.81% (89 runs sampled) - // hessian1 decode with type: date x 2,797,789 ops/sec ±1.82% (91 runs sampled) - // hessian2 decode with type: date x 2,541,107 ops/sec ±2.40% (92 runs sampled) - // hessian1 decode with type: long x 3,869,288 ops/sec ±1.31% (93 runs sampled) - // hessian2 decode with type: long x 5,598,654 ops/sec ±1.20% (95 runs sampled) - // hessian1 decode with type: string x 1,068,879 ops/sec ±1.89% (92 runs sampled) - // hessian2 decode with type: string x 1,110,680 ops/sec ±0.89% (94 runs sampled) - // hessian1 decode with type: [1, 2, 3] x 1,150,027 ops/sec ±0.94% (94 runs sampled) - // hessian2 decode with type: [1, 2, 3] x 1,834,472 ops/sec ±0.90% (94 runs sampled) - // hessian1 decode with type array x 508,028 ops/sec ±0.99% (96 runs sampled) - // hessian2 decode with type array x 605,446 ops/sec ±1.10% (96 runs sampled) - // hessian1 decode with type: simple object x 190,014 ops/sec ±0.92% (97 runs sampled) - // hessian2 decode with type: simple object x 176,337 ops/sec ±1.16% (96 runs sampled) - // hessian1 decode with type: complex object x 133,815 ops/sec ±1.21% (90 runs sampled) - // hessian2 decode with type: complex object x 134,814 ops/sec ±0.96% (96 runs sampled) + benchmarks.add(event.target); + }) + .on('start', function(event) { + console.log('\n Hessian Decode Benchmark\n node version: %s, date: %s\n Starting...', + process.version, Date()); + }) + .on('complete', function done() { + benchmarks.log(); + }) + .run({ 'async': false }); + +// Hessian Decode Benchmark +// node version: v0.11.12, date: Wed Jun 25 2014 10:46:26 GMT+0800 (CST) +// Starting... +// 32 tests completed. + +// hessian1 decode: number x 5,983,374 ops/sec ±1.57% (94 runs sampled) +// hessian2 decode: number x 5,713,562 ops/sec ±2.22% (91 runs sampled) +// hessian1 decode: date x 2,959,698 ops/sec ±1.19% (92 runs sampled) +// hessian2 decode: date x 2,548,345 ops/sec ±1.32% (91 runs sampled) +// hessian1 decode: long x 3,880,734 ops/sec ±1.66% (93 runs sampled) +// hessian2 decode: long x 5,221,659 ops/sec ±2.30% (90 runs sampled) +// hessian1 decode: string x 1,109,890 ops/sec ±1.59% (94 runs sampled) +// hessian2 decode: string x 1,075,246 ops/sec ±1.43% (92 runs sampled) +// hessian1 decode: [1, 2, 3] x 1,186,054 ops/sec ±1.24% (92 runs sampled) +// hessian2 decode: [1, 2, 3] x 2,049,867 ops/sec ±1.75% (98 runs sampled) +// hessian1 decode array x 511,250 ops/sec ±0.92% (95 runs sampled) +// hessian2 decode array x 600,564 ops/sec ±1.27% (90 runs sampled) +// hessian1 decode: simple object x 186,875 ops/sec ±0.81% (93 runs sampled) +// hessian2 decode: simple object x 174,768 ops/sec ±0.74% (94 runs sampled) +// hessian1 decode: complex object x 133,573 ops/sec ±1.31% (97 runs sampled) +// hessian2 decode: complex object x 131,234 ops/sec ±1.45% (95 runs sampled) +// hessian1 decode with type: number x 5,014,602 ops/sec ±1.97% (91 runs sampled) +// hessian2 decode with type: number x 5,890,098 ops/sec ±1.81% (89 runs sampled) +// hessian1 decode with type: date x 2,797,789 ops/sec ±1.82% (91 runs sampled) +// hessian2 decode with type: date x 2,541,107 ops/sec ±2.40% (92 runs sampled) +// hessian1 decode with type: long x 3,869,288 ops/sec ±1.31% (93 runs sampled) +// hessian2 decode with type: long x 5,598,654 ops/sec ±1.20% (95 runs sampled) +// hessian1 decode with type: string x 1,068,879 ops/sec ±1.89% (92 runs sampled) +// hessian2 decode with type: string x 1,110,680 ops/sec ±0.89% (94 runs sampled) +// hessian1 decode with type: [1, 2, 3] x 1,150,027 ops/sec ±0.94% (94 runs sampled) +// hessian2 decode with type: [1, 2, 3] x 1,834,472 ops/sec ±0.90% (94 runs sampled) +// hessian1 decode with type array x 508,028 ops/sec ±0.99% (96 runs sampled) +// hessian2 decode with type array x 605,446 ops/sec ±1.10% (96 runs sampled) +// hessian1 decode with type: simple object x 190,014 ops/sec ±0.92% (97 runs sampled) +// hessian2 decode with type: simple object x 176,337 ops/sec ±1.16% (96 runs sampled) +// hessian1 decode with type: complex object x 133,815 ops/sec ±1.21% (90 runs sampled) +// hessian2 decode with type: complex object x 134,814 ops/sec ±0.96% (96 runs sampled) diff --git a/index.js b/index.js index b1df0e8..00b9f49 100644 --- a/index.js +++ b/index.js @@ -18,6 +18,9 @@ var DecoderV2 = exports.DecoderV2 = require('./lib/v2/decoder'); exports.encoderV1 = new EncoderV1({size: 1024 * 1024}); exports.encoderV2 = new EncoderV2({size: 1024 * 1024}); +var decoderV1 = new DecoderV1(); +var decoderV2 = new DecoderV2(); + exports.decode = function decode(buf, version, withType) { if (typeof version === 'boolean') { // buf, withType, version @@ -29,9 +32,9 @@ exports.decode = function decode(buf, version, withType) { withType = !!withType; if (version === '2.0') { - return new DecoderV2(buf).read(withType); + return decoderV2.init(buf).read(withType); } - return new DecoderV1(buf).read(withType); + return decoderV1.init(buf).read(withType); }; exports.encode = function encode(obj, version) { diff --git a/lib/v1/decoder.js b/lib/v1/decoder.js index 54599c6..a60372e 100644 --- a/lib/v1/decoder.js +++ b/lib/v1/decoder.js @@ -19,9 +19,10 @@ var JavaExceptionError = object.JavaExceptionError; var supportES6Map = require('../utils').supportES6Map; var BYTE_CODES = {}; +var EMPTY_BYTES = new Buffer(0); function Decoder(buf) { - this.byteBuffer = buf ? ByteBuffer.wrap(buf) : null; + this.byteBuffer = ByteBuffer.wrap(buf || EMPTY_BYTES); this.refMap = {}; this.refId = 0; this.BYTE_CODES = BYTE_CODES; @@ -47,7 +48,10 @@ proto._addRef = function (obj) { * @api public */ proto.init = function (buf) { - this.byteBuffer = ByteBuffer.wrap(buf); + this.byteBuffer._bytes = buf; + this.byteBuffer.reset(); + this.refMap = {}; + this.refId = 0; return this; }; @@ -92,7 +96,7 @@ proto.handleType = function(type, val, withType) { * @api public */ proto.readNull = function () { - this._checkLabel('readNull', 'N'); + this.byteBuffer.skip(1); return null; }; @@ -200,7 +204,7 @@ utils.addByteCodes(BYTE_CODES, [ */ proto.readDate = function (withType) { this._checkLabel('readDate', 'd'); - var date = utils.handleLong(this.byteBuffer.getLong()); + var date = this.byteBuffer.getLong().toNumber(); debug('read a date with milliEpoch: %d', date); var val = new Date(date); return this.handleType('date', val, withType); @@ -250,25 +254,27 @@ proto._readUTF8String = function (len) { if (!is.number(len)) { len = this.byteBuffer.getUInt16(); } - - var startPos = this.byteBuffer.position(); - var head; - var l; debug('read utf8 string tunk, chars length: %d', len); - - if (len === 0) { - return ''; - } - + var data = []; + var ch; + var v; while (len--) { - head = this.byteBuffer.get(); - l = utils.lengthOfUTF8(head); - this.byteBuffer.skip(l - 1); + ch = this.byteBuffer.get(); + if (ch < 0x80) { + v = ch; + } else if ((ch & 0xe0) === 0xc0) { + var ch1 = this.byteBuffer.get(); + v = ((ch & 0x1f) << 6) + (ch1 & 0x3f); + } else if ((ch & 0xf0) === 0xe0) { + var ch1 = this.byteBuffer.get(); + var ch2 = this.byteBuffer.get(); + v = ((ch & 0x0f) << 12) + ((ch1 & 0x3f) << 6) + (ch2 & 0x3f); + } else { + throw new Error('string is not valid UTF-8 encode'); + } + data.push(v); } - debug('get string trunk. start position: %d, byte length: %d', - startPos, this.byteBuffer.position() - startPos); - - return this.byteBuffer.getRawString(startPos, this.byteBuffer.position() - startPos); + return String.fromCharCode.apply(String, data); }; /** @@ -319,7 +325,7 @@ utils.addByteCodes(BYTE_CODES, [ * @return {String} type string */ proto.readType = function (skip) { - this._checkLabel('readType', 't'); + this.byteBuffer.skip(1); var typeLength = this.byteBuffer.getUInt16(); if (skip) { this.byteBuffer.skip(typeLength); @@ -337,7 +343,7 @@ utils.addByteCodes(BYTE_CODES, [ ], 'readType'); proto.readLength = function () { - this._checkLabel('readLength', 'l'); // x6c + this.byteBuffer.skip(1); var len = this.byteBuffer.getUInt(); debug('read length: %s', len); return len; @@ -388,7 +394,7 @@ proto._readSparseObject = function (withType) { * @api public */ proto.readObject = function (withType) { - this._checkLabel('readObject', 'M'); + this.byteBuffer.skip(1); debug('start read an object'); var typeLabel = this.byteBuffer.getChar(this.byteBuffer.position()); if (typeLabel !== 't') { @@ -496,7 +502,7 @@ proto._readNoLengthArray = function (withType, type) { */ proto.readArray = function (withType) { debug('start read an array'); - this._checkLabel('readArray', 'V'); + this.byteBuffer.skip(1); var type = ''; var typeLabel = this.byteBuffer.getChar(this.byteBuffer.position()); @@ -566,7 +572,7 @@ proto.readRef = function (withType) { * @return {Number} */ proto.readRefId = function (withType) { - this._checkLabel('readRef', 'R'); + this.byteBuffer.skip(1); return this.byteBuffer.getInt(); }; diff --git a/lib/v2/decoder.js b/lib/v2/decoder.js index 98e4da7..a5f39a2 100644 --- a/lib/v2/decoder.js +++ b/lib/v2/decoder.js @@ -36,6 +36,13 @@ util.inherits(Decoder, DecoderV1); var proto = Decoder.prototype; +proto.init = function (buf) { + DecoderV1.prototype.init.call(this, buf); + this.types = []; + this.classes = []; + return this; +}; + proto.clean = function () { DecoderV1.prototype.clean.call(this); this.types = []; @@ -299,7 +306,7 @@ utils.addByteCodes(BYTE_CODES, [ proto.readDate = function () { var code = this.byteBuffer.get(); if (code === 0x4a) { - return new Date(utils.handleLong(this.byteBuffer.getLong())); + return new Date(this.byteBuffer.getLong().toNumber()); } if (code === 0x4b) { return new Date(this.byteBuffer.getInt32() * 60000); diff --git a/package.json b/package.json index 5d5d947..7d557d8 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "test-cov": "istanbul cover _mocha -- -t 15000 -r should test/*.test.js", "lint": "jshint .", "ci": "npm run lint && npm run test-cov", - "autod": "autod -w --prefix '~' -e benchmark", + "autod": "autod -w --prefix '^' -e benchmark", "benchmark": "node benchmark/encode.js && node benchmark/decode.js" }, "repository": { @@ -34,21 +34,21 @@ }, "homepage": "https://github.com/node-modules/hessian.js", "dependencies": { - "byte": "~1.1.5", - "debug": "~2.2.0", - "is-type-of": "~1.0.0", - "utility": "~1.6.0", - "long": "~3.1.0" + "byte": "^1.1.6", + "debug": "^2.6.6", + "is-type-of": "^1.0.0", + "long": "^3.2.0", + "utility": "^1.12.0" }, "devDependencies": { "autod": "*", "beautify-benchmark": "*", "benchmark": "*", "istanbul": "*", - "js-to-java": "2", + "js-to-java": "^2.4.0", "jshint": "*", "mocha": "*", - "should": "10" + "should": "^11.2.1" }, "engines": { "node": ">= 0.12.0"