Skip to content

Commit 4d3b48e

Browse files
authored
feat: support cache class for v2/decode (#90)
1 parent 8e8cfc6 commit 4d3b48e

9 files changed

Lines changed: 472 additions & 18 deletions

File tree

.jshintrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
"lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block
5050
"laxbreak" : true, // true: Tolerate possibly unsafe line breakings
5151
"laxcomma" : false, // true: Tolerate comma-first style coding
52-
"loopfunc" : false, // true: Tolerate functions being defined in loops
52+
"loopfunc" : true, // true: Tolerate functions being defined in loops
5353
"multistr" : true, // true: Tolerate multi-line strings
5454
"proto" : false, // true: Tolerate using the `__proto__` property
5555
"scripturl" : false, // true: Tolerate script-targeted URLs

benchmark/buf.txt

2.45 KB
Binary file not shown.

benchmark/cache.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
'use strict';
2+
3+
var Benchmark = require('benchmark');
4+
var benchmarks = require('beautify-benchmark');
5+
var path = require('path');
6+
var fs = require('fs');
7+
var assert = require('assert');
8+
var hessian = require('../');
9+
10+
11+
var buf = fs.readFileSync(path.join(__dirname, 'buf.txt'));
12+
var cache = new Map();
13+
14+
assert.deepEqual(hessian.decode(buf, '2.0', { classCache: cache }), hessian.decode(buf, '2.0'));
15+
assert.deepEqual(hessian.decode(buf, '2.0', { classCache: cache }), hessian.decode(buf, '2.0'));
16+
var suite = new Benchmark.Suite();
17+
18+
suite
19+
20+
.add('with cache', function() {
21+
hessian.decode(buf, '2.0', { classCache: cache });
22+
})
23+
.add('without cache', function() {
24+
hessian.decode(buf, '2.0');
25+
})
26+
27+
.on('cycle', function(event) {
28+
benchmarks.add(event.target);
29+
})
30+
.on('start', function(event) {
31+
console.log('\n Cache Benchmark\n node version: %s, date: %s\n Starting...',
32+
process.version, Date());
33+
})
34+
.on('complete', function done() {
35+
benchmarks.log();
36+
})
37+
.run({ 'async': false });
38+
39+
// Cache Benchmark
40+
// node version: v8.5.0, date: Tue Oct 31 2017 17:17:54 GMT+0800 (CST)
41+
// Starting...
42+
// 2 tests completed.
43+
//
44+
// with cache x 14,006 ops/sec ±2.21% (83 runs sampled)
45+
// without cache x 5,506 ops/sec ±1.94% (83 runs sampled)

benchmark/reg.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
'use strict';
2+
3+
var Benchmark = require('benchmark');
4+
var benchmarks = require('beautify-benchmark');
5+
6+
var suite = new Benchmark.Suite();
7+
8+
9+
var INNER_CLASS_PROPERTY_REG = /^this\$\d+$/;
10+
var INNER_CLASS_LABEL = '$$ignore_inner_property$$';
11+
12+
var name1 = 'foobar';
13+
var name2 = 'this$123';
14+
var name3 = INNER_CLASS_LABEL;
15+
16+
suite
17+
18+
.add('dynamic', function() {
19+
/^this\$\d+/.test(name1);
20+
/^this\$\d+/.test(name2);
21+
})
22+
.add('static', function() {
23+
INNER_CLASS_PROPERTY_REG.test(name1);
24+
INNER_CLASS_PROPERTY_REG.test(name2);
25+
})
26+
.add('equal', function() {
27+
name1 === INNER_CLASS_LABEL;
28+
name3 === INNER_CLASS_LABEL;
29+
})
30+
31+
.on('cycle', function(event) {
32+
benchmarks.add(event.target);
33+
})
34+
.on('start', function(event) {
35+
console.log('\n Reg Benchmark\n node version: %s, date: %s\n Starting...',
36+
process.version, Date());
37+
})
38+
.on('complete', function done() {
39+
benchmarks.log();
40+
})
41+
.run({ 'async': false });
42+
43+
// node version: v8.5.0, date: Sat Oct 21 2017 08:00:33 GMT+0800 (CST)
44+
// Starting...
45+
// 3 tests completed.
46+
//
47+
// dynamic x 8,355,274 ops/sec ±1.00% (85 runs sampled)
48+
// static x 10,547,340 ops/sec ±0.90% (89 runs sampled)
49+
// equal x 488,054,083 ops/sec ±0.67% (88 runs sampled)

index.js

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,30 @@ var DecoderV2 = exports.DecoderV2 = require('./lib/v2/decoder');
1818
exports.encoderV1 = new EncoderV1({size: 1024 * 1024});
1919
exports.encoderV2 = new EncoderV2({size: 1024 * 1024});
2020

21-
exports.decode = function decode(buf, version, withType) {
22-
if (typeof version === 'boolean') {
21+
exports.decode = function decode(buf, version, options) {
22+
var classCache;
23+
var withType;
24+
25+
if (version && typeof version !== 'string') {
2326
// buf, withType, version
2427
var t = version;
25-
version = withType;
26-
withType = t;
28+
version = options;
29+
options = t;
2730
}
2831

32+
if (typeof options === 'boolean') {
33+
withType = options;
34+
}
35+
if (typeof options === 'object') {
36+
withType = options.withType;
37+
classCache = options.classCache;
38+
}
2939
withType = !!withType;
3040

3141
if (version === '2.0') {
32-
return new DecoderV2(buf).read(withType);
42+
return new DecoderV2(buf, classCache).read(withType);
3343
}
34-
return new DecoderV1(buf).read(withType);
44+
return new DecoderV1(buf, classCache).read(withType);
3545
};
3646

3747
exports.encode = function encode(obj, version) {

lib/v1/decoder.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,6 @@ proto._readUTF8String = function (len) {
250250
if (!is.number(len)) {
251251
len = this.byteBuffer.getUInt16();
252252
}
253-
254253
var startPos = this.byteBuffer.position();
255254
var head;
256255
var l;

lib/v2/decoder.js

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,14 @@ var supportES6Map = require('../utils').supportES6Map;
2424

2525
var BYTE_CODES = {};
2626

27-
function Decoder(buf) {
27+
function Decoder(buf, classCache) {
2828
DecoderV1.call(this, buf);
2929
this.BYTE_CODES = BYTE_CODES;
3030
this.classes = []; // {name: classname, fields: []}
3131
this.types = [];
3232

3333
this._isLastChunk = false;
34+
this.classCache = classCache;
3435
}
3536

3637
util.inherits(Decoder, DecoderV1);
@@ -511,18 +512,45 @@ proto.readType = function () {
511512
return type;
512513
};
513514

515+
// properties match with this$\d+ means inner properties
516+
// it is useless for node and is circular structure
517+
var INNER_CLASS_PROPERTY_REG = /^this\$\d+$/;
518+
var INNER_CLASS_LABEL = '$$ignore_inner_property$$';
519+
514520
proto._readObjectDefinition = function () {
515521
var classname = this.readString();
522+
523+
// get class definition from cache
524+
var cacheClz = this.classCache && this.classCache.get(classname);
525+
if (cacheClz) {
526+
this.byteBuffer.skip(cacheClz.length);
527+
this.classes.push(cacheClz);
528+
return cacheClz;
529+
}
530+
531+
var pos = this.byteBuffer.position();
516532
var fieldsLength = this.readInt();
517533
var fields = [];
518534
for (var i = 0; i < fieldsLength; i++) {
519-
fields.push(this.readString());
535+
var name = this.readString();
536+
if (INNER_CLASS_PROPERTY_REG.test(name)) {
537+
name = INNER_CLASS_LABEL;
538+
}
539+
fields.push(name);
520540
}
521541
debug('_readObjectDefinition got %s fields: %j', classname, fields);
522-
this.classes.push({
542+
var clz = {
523543
name: classname,
524-
fields: fields
525-
});
544+
fields: fields,
545+
length: this.byteBuffer.position() - pos,
546+
};
547+
548+
this.classes.push(clz);
549+
// set class definition into cache
550+
if (this.classCache) {
551+
this.classCache.set(clz.name, clz);
552+
}
553+
return clz;
526554
};
527555

528556
/**
@@ -572,7 +600,7 @@ proto.readObject = function (withType) {
572600
} else {
573601
this.throwError('readObject', code);
574602
}
575-
603+
576604
var cls = this.classes[ref];
577605
debug('readObject %s, ref: %s', cls.name, ref);
578606

@@ -586,7 +614,7 @@ proto.readObject = function (withType) {
586614
for (var i = 0; i < fields.length; i++) {
587615
var name = fields[i];
588616
var value = this.read(withType);
589-
if (!/^this\$\d+$/.test(name)) {
617+
if (name !== INNER_CLASS_LABEL) {
590618
result.$[name] = value;
591619
}
592620
}
@@ -773,7 +801,7 @@ utils.addByteCodes(BYTE_CODES, [
773801
* v2.0
774802
* ```
775803
* map ::= M(x4d) [type] (value value)* Z
776-
*
804+
*
777805
* @see http://hessian.caucho.com/doc/hessian-serialization.html##map
778806
* ```
779807
* Represents serialized maps and can represent objects.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@
4747
"istanbul": "*",
4848
"js-to-java": "2",
4949
"jshint": "*",
50-
"mocha": "*"
50+
"mocha": "2"
5151
},
5252
"engines": {
5353
"node": ">= 0.12.0"
5454
}
55-
}
55+
}

0 commit comments

Comments
 (0)