Skip to content

Commit a24e8c3

Browse files
committed
benchmarks
1 parent bf07776 commit a24e8c3

File tree

5 files changed

+406
-2
lines changed

5 files changed

+406
-2
lines changed

benchmark/index.js

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
var Benchmark = require('benchmark');
2+
var assert = require('assert');
3+
var suite = new Benchmark.Suite;
4+
5+
var Parser = require('./../lib/parser');
6+
var ParserOLD = require('./old/parser');
7+
8+
function returnError(error) {
9+
// throw error; silent for err error perf test
10+
}
11+
12+
function checkReply() {
13+
}
14+
15+
var startBuffer = new Buffer('$100\r\nabcdefghij');
16+
var chunkBuffer = new Buffer('abcdefghijabcdefghijabcdefghij');
17+
var stringBuffer = new Buffer('+testing a simple string\r\n');
18+
var integerBuffer = new Buffer(':1237884\r\n');
19+
var errorBuffer = new Buffer('-Error ohnoesitbroke\r\n');
20+
var arrayBuffer = new Buffer('*1\r\n*1\r\n$1\r\na\r\n');
21+
var endBuffer = new Buffer('\r\n');
22+
23+
var parserOld = new ParserOLD({
24+
returnReply: checkReply,
25+
returnError: returnError,
26+
returnFatalError: returnError,
27+
name: 'javascript'
28+
});
29+
30+
var parserHiRedis = new Parser({
31+
returnReply: checkReply,
32+
returnError: returnError,
33+
returnFatalError: returnError,
34+
name: 'hiredis'
35+
});
36+
37+
var parser = new Parser({
38+
returnReply: checkReply,
39+
returnError: returnError,
40+
returnFatalError: returnError,
41+
name: 'javascript'
42+
});
43+
44+
// BULK STRINGS
45+
46+
suite.add('OLD CODE: multiple chunks in a bulk string', function () {
47+
parserOld.execute(startBuffer);
48+
parserOld.execute(chunkBuffer);
49+
parserOld.execute(chunkBuffer);
50+
parserOld.execute(chunkBuffer);
51+
parserOld.execute(endBuffer);
52+
});
53+
54+
suite.add('HIREDIS: multiple chunks in a bulk string', function () {
55+
parserHiRedis.execute(startBuffer);
56+
parserHiRedis.execute(chunkBuffer);
57+
parserHiRedis.execute(chunkBuffer);
58+
parserHiRedis.execute(chunkBuffer);
59+
parserHiRedis.execute(endBuffer);
60+
});
61+
62+
suite.add('NEW CODE: multiple chunks in a bulk string', function () {
63+
parser.execute(startBuffer);
64+
parser.execute(chunkBuffer);
65+
parser.execute(chunkBuffer);
66+
parser.execute(chunkBuffer);
67+
parser.execute(endBuffer);
68+
});
69+
70+
// STRINGS
71+
72+
suite.add('\nOLD CODE: + simple string', function () {
73+
parserOld.execute(stringBuffer);
74+
});
75+
76+
suite.add('HIREDIS: + simple string', function () {
77+
parserHiRedis.execute(stringBuffer);
78+
});
79+
80+
suite.add('NEW CODE: + simple string', function () {
81+
parser.execute(stringBuffer);
82+
});
83+
84+
// INTEGERS
85+
86+
suite.add('\nOLD CODE: + integer', function () {
87+
parserOld.execute(integerBuffer);
88+
});
89+
90+
suite.add('HIREDIS: + integer', function () {
91+
parserHiRedis.execute(integerBuffer);
92+
});
93+
94+
suite.add('NEW CODE: + integer', function () {
95+
parser.execute(integerBuffer);
96+
});
97+
98+
// ARRAYS
99+
100+
suite.add('\nOLD CODE: * array', function () {
101+
parserOld.execute(arrayBuffer);
102+
});
103+
104+
suite.add('HIREDIS: * array', function () {
105+
parserHiRedis.execute(arrayBuffer);
106+
});
107+
108+
suite.add('NEW CODE: * array', function () {
109+
parser.execute(arrayBuffer);
110+
});
111+
112+
113+
// ERRORS
114+
115+
suite.add('\nOLD CODE: * error', function () {
116+
parserOld.execute(errorBuffer);
117+
});
118+
119+
suite.add('HIREDIS: * error', function () {
120+
parserHiRedis.execute(errorBuffer);
121+
});
122+
123+
suite.add('NEW CODE: * error', function () {
124+
parser.execute(errorBuffer);
125+
});
126+
127+
128+
// add listeners
129+
suite.on('cycle', function (event) {
130+
console.log(String(event.target));
131+
});
132+
133+
suite.on('complete', function () {
134+
console.log('\n\nFastest is ' + this.filter('fastest').map('name'));
135+
});
136+
137+
138+
suite.run({delay:2, minSamples: 100 });

benchmark/old/hiredis.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
'use strict';
2+
3+
var hiredis = require('hiredis');
4+
5+
function HiredisReplyParser(options) {
6+
this.name = 'hiredis';
7+
this.options = options;
8+
this.reader = new hiredis.Reader(options);
9+
}
10+
11+
HiredisReplyParser.prototype.parseData = function () {
12+
try {
13+
return this.reader.get();
14+
} catch (err) {
15+
// Protocol errors land here
16+
// Reset the parser. Otherwise new commands can't be processed properly
17+
this.reader = new hiredis.Reader(this.options);
18+
this.returnFatalError(err);
19+
}
20+
};
21+
22+
HiredisReplyParser.prototype.execute = function (data) {
23+
this.reader.feed(data);
24+
var reply = this.parseData();
25+
26+
while (reply !== undefined) {
27+
if (reply && reply.name === 'Error') {
28+
this.returnError(reply);
29+
} else {
30+
this.returnReply(reply);
31+
}
32+
reply = this.parseData();
33+
}
34+
};
35+
36+
module.exports = HiredisReplyParser;

benchmark/old/javascript.js

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
'use strict';
2+
3+
function JavascriptReplyParser(options) {
4+
this.name = 'javascript_old';
5+
this.buffer = new Buffer(0);
6+
this.offset = 0;
7+
this.bigStrSize = 0;
8+
this.chunksSize = 0;
9+
this.buffers = [];
10+
this.type = 0;
11+
this.protocolError = false;
12+
this.offsetCache = 0;
13+
// If returnBuffers is active, all return values are returned as buffers besides numbers and errors
14+
if (options.return_buffers) {
15+
this.handleReply = function (start, end) {
16+
return this.buffer.slice(start, end);
17+
};
18+
} else {
19+
this.handleReply = function (start, end) {
20+
return this.buffer.toString('utf-8', start, end);
21+
};
22+
}
23+
// If stringNumbers is activated the parser always returns numbers as string
24+
// This is important for big numbers (number > Math.pow(2, 53)) as js numbers are 64bit floating point numbers with reduced precision
25+
if (options.string_numbers) {
26+
this.handleNumbers = function (start, end) {
27+
return this.buffer.toString('ascii', start, end);
28+
};
29+
} else {
30+
this.handleNumbers = function (start, end) {
31+
return +this.buffer.toString('ascii', start, end);
32+
};
33+
}
34+
}
35+
36+
JavascriptReplyParser.prototype.parseResult = function (type) {
37+
var start = 0,
38+
end = 0,
39+
packetHeader = 0,
40+
reply;
41+
42+
if (type === 36) { // $
43+
packetHeader = this.parseHeader();
44+
// Packets with a size of -1 are considered null
45+
if (packetHeader === -1) {
46+
return null;
47+
}
48+
end = this.offset + packetHeader;
49+
start = this.offset;
50+
if (end + 2 > this.buffer.length) {
51+
this.buffers.push(this.offsetCache === 0 ? this.buffer : this.buffer.slice(this.offsetCache));
52+
this.chunksSize = this.buffers[0].length;
53+
// Include the packetHeader delimiter
54+
this.bigStrSize = packetHeader + 2;
55+
throw new Error('Wait for more data.');
56+
}
57+
// Set the offset to after the delimiter
58+
this.offset = end + 2;
59+
return this.handleReply(start, end);
60+
} else if (type === 58) { // :
61+
// Up to the delimiter
62+
end = this.packetEndOffset();
63+
start = this.offset;
64+
// Include the delimiter
65+
this.offset = end + 2;
66+
// Return the coerced numeric value
67+
return this.handleNumbers(start, end);
68+
} else if (type === 43) { // +
69+
end = this.packetEndOffset();
70+
start = this.offset;
71+
this.offset = end + 2;
72+
return this.handleReply(start, end);
73+
} else if (type === 42) { // *
74+
packetHeader = this.parseHeader();
75+
if (packetHeader === -1) {
76+
return null;
77+
}
78+
reply = [];
79+
for (var i = 0; i < packetHeader; i++) {
80+
if (this.offset >= this.buffer.length) {
81+
throw new Error('Wait for more data.');
82+
}
83+
reply.push(this.parseResult(this.buffer[this.offset++]));
84+
}
85+
return reply;
86+
} else if (type === 45) { // -
87+
end = this.packetEndOffset();
88+
start = this.offset;
89+
this.offset = end + 2;
90+
return new Error(this.buffer.toString('utf-8', start, end));
91+
}
92+
};
93+
94+
JavascriptReplyParser.prototype.execute = function (buffer) {
95+
if (this.chunksSize !== 0) {
96+
if (this.bigStrSize > this.chunksSize + buffer.length) {
97+
this.buffers.push(buffer);
98+
this.chunksSize += buffer.length;
99+
return;
100+
}
101+
this.buffers.push(buffer);
102+
this.buffer = Buffer.concat(this.buffers, this.chunksSize + buffer.length);
103+
this.buffers = [];
104+
this.bigStrSize = 0;
105+
this.chunksSize = 0;
106+
} else if (this.offset >= this.buffer.length) {
107+
this.buffer = buffer;
108+
} else {
109+
this.buffer = Buffer.concat([this.buffer.slice(this.offset), buffer]);
110+
}
111+
this.offset = 0;
112+
this.run();
113+
};
114+
115+
JavascriptReplyParser.prototype.tryParsing = function () {
116+
try {
117+
return this.parseResult(this.type);
118+
} catch (err) {
119+
// Catch the error (not enough data), rewind if it's an array,
120+
// and wait for the next packet to appear
121+
this.offset = this.offsetCache;
122+
// Indicate that there's no protocol error by resetting the type too
123+
this.type = undefined;
124+
}
125+
};
126+
127+
JavascriptReplyParser.prototype.run = function () {
128+
// Set a rewind point. If a failure occurs, wait for the next execute()/append() and try again
129+
this.offsetCache = this.offset;
130+
this.type = this.buffer[this.offset++];
131+
var reply = this.tryParsing();
132+
133+
while (reply !== undefined) {
134+
if (this.type === 45) { // Errors -
135+
this.returnError(reply);
136+
} else {
137+
this.returnReply(reply); // Strings + // Integers : // Bulk strings $ // Arrays *
138+
}
139+
this.offsetCache = this.offset;
140+
this.type = this.buffer[this.offset++];
141+
reply = this.tryParsing();
142+
}
143+
if (this.type !== undefined) {
144+
// Reset the buffer so the parser can handle following commands properly
145+
this.buffer = new Buffer(0);
146+
this.returnFatalError(new Error('Protocol error, got ' + JSON.stringify(String.fromCharCode(this.type)) + ' as reply type byte'));
147+
}
148+
};
149+
150+
JavascriptReplyParser.prototype.parseHeader = function () {
151+
var end = this.packetEndOffset(),
152+
value = this.buffer.toString('ascii', this.offset, end) | 0;
153+
154+
this.offset = end + 2;
155+
return value;
156+
};
157+
158+
JavascriptReplyParser.prototype.packetEndOffset = function () {
159+
var offset = this.offset,
160+
len = this.buffer.length - 1;
161+
162+
while (this.buffer[offset] !== 0x0d && this.buffer[offset + 1] !== 0x0a) {
163+
offset++;
164+
165+
if (offset >= len) {
166+
throw new Error('Did not see LF after NL reading multi bulk count (' + offset + ' => ' + this.buffer.length + ', ' + this.offset + ')');
167+
}
168+
}
169+
return offset;
170+
};
171+
172+
module.exports = JavascriptReplyParser;

0 commit comments

Comments
 (0)