Skip to content

Commit 06a0562

Browse files
author
Ruben Bridgewater
committed
Improve chunked array parsing significantly
1 parent 94a3a4c commit 06a0562

File tree

3 files changed

+102
-24
lines changed

3 files changed

+102
-24
lines changed

benchmark/index.js

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ var stringBuffer = new Buffer('+testing a simple string\r\n')
3636
var integerBuffer = new Buffer(':1237884\r\n')
3737
var bigIntegerBuffer = new Buffer(':184467440737095516171234567890\r\n') // 2^64 + 1
3838
var errorBuffer = new Buffer('-Error ohnoesitbroke\r\n')
39-
var arrayBuffer = new Buffer('*1\r\n*1\r\n$1\r\na\r\n')
4039
var endBuffer = new Buffer('\r\n')
4140
var lorem = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, ' +
4241
'sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ' +
@@ -49,15 +48,29 @@ for (var i = 0; i < 64; i++) {
4948
chunks[i] = new Buffer(shuffle(bigStringArray).join(' ') + '.') // Math.pow(2, 16) chars long
5049
}
5150

52-
var bigArraySize = 100
53-
var bigArray = '*' + bigArraySize + '\r\n'
51+
var arraySize = 100
52+
var array = '*' + arraySize + '\r\n'
53+
var size = 0
54+
for (i = 0; i < arraySize; i++) {
55+
array += '$'
56+
size = (Math.random() * 10 | 0) + 1
57+
array += size + '\r\n' + lorem.slice(0, size) + '\r\n'
58+
}
59+
60+
var arrayBuffer = new Buffer(array)
61+
62+
var bigArraySize = 1000
63+
var bigArrayChunks = [new Buffer('*' + bigArraySize)]
5464
for (i = 0; i < bigArraySize; i++) {
55-
bigArray += '$'
56-
var size = (Math.random() * 10 | 0) + 1
57-
bigArray += size + '\r\n' + lorem.slice(0, size) + '\r\n'
65+
size = (Math.random() * 10000 | 0)
66+
if (i % 2) {
67+
bigArrayChunks.push(new Buffer('\r\n$' + size + '\r\n' + Array(size + 1).join('a')))
68+
} else {
69+
bigArrayChunks.push(new Buffer('\r\n+' + Array(size + 1).join('b')))
70+
}
5871
}
72+
bigArrayChunks.push(new Buffer('\r\n'))
5973

60-
var bigArrayBuffer = new Buffer(bigArray)
6174
var chunkedStringPart1 = new Buffer('+foobar')
6275
var chunkedStringPart2 = new Buffer('bazEND\r\n')
6376

@@ -257,22 +270,24 @@ suite.add('NEW BUF: * array', function () {
257270
parserBuffer.execute(arrayBuffer)
258271
})
259272

260-
// BIG ARRAYS
261-
262-
suite.add('\nOLD CODE: * bigArray', function () {
263-
parserOld.execute(bigArrayBuffer)
264-
})
273+
// BIG ARRAYS (running the old parser is to slow)
265274

266275
suite.add('HIREDIS: * bigArray', function () {
267-
parserHiRedis.execute(bigArrayBuffer)
276+
for (var i = 0; i < bigArrayChunks.length; i++) {
277+
parserHiRedis.execute(bigArrayChunks[i])
278+
}
268279
})
269280

270281
suite.add('NEW CODE: * bigArray', function () {
271-
parser.execute(bigArrayBuffer)
282+
for (var i = 0; i < bigArrayChunks.length; i++) {
283+
parser.execute(bigArrayChunks[i])
284+
}
272285
})
273286

274287
suite.add('NEW BUF: * bigArray', function () {
275-
parserBuffer.execute(bigArrayBuffer)
288+
for (var i = 0; i < bigArrayChunks.length; i++) {
289+
parserBuffer.execute(bigArrayChunks[i])
290+
}
276291
})
277292

278293
// ERRORS

changelog.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,19 @@
1-
## v.2.2.0 - 18 Nov, 2016
1+
## v.2.3.0 - xx Nov, 2016
2+
3+
Features
4+
5+
- Parsing time for big arrays (e.g. 4mb+) is now linear and works well for arbitrary array sizes
6+
7+
This case is a magnitude faster than before
8+
9+
OLD STR: * big array x 1.09 ops/sec ±2.15% (7 runs sampled)
10+
OLD BUF: * big array x 1.23 ops/sec ±2.67% (8 runs sampled)
11+
12+
NEW STR: * big array x 273 ops/sec ±2.09% (85 runs sampled)
13+
NEW BUF: * big array x 259 ops/sec ±1.32% (85 runs sampled)
14+
(~10mb array with 1000 entries)
15+
16+
## v.2.2.0 - 31 Oct, 2016
217

318
Features
419

lib/parser.js

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -205,18 +205,45 @@ function parseArray (parser) {
205205
if (length === -1) {
206206
return null
207207
}
208-
209208
var responses = new Array(length)
210-
var bufferLength = parser.buffer.length - 3
211-
for (var i = 0; i < length; i++) {
209+
return parseArrayElements(parser, responses, 0)
210+
}
211+
212+
/**
213+
* Parse chunked redis array response
214+
* @param parser
215+
* @returns {*}
216+
*/
217+
function parseArrayChunks (parser) {
218+
return parseArrayElements(parser, parser.arrayCache, parser.arrayPos)
219+
}
220+
221+
/**
222+
* Parse redis array response elements
223+
* @param parser
224+
* @param responses
225+
* @param i
226+
* @returns {*}
227+
*/
228+
function parseArrayElements (parser, responses, i) {
229+
var bufferLength = parser.buffer.length
230+
while (i < responses.length) {
231+
var offset = parser.offset
212232
if (parser.offset >= bufferLength) {
233+
parser.arrayCache = responses
234+
parser.arrayPos = i
235+
parser.offset = offset
213236
return
214237
}
215238
var response = parseType(parser, parser.buffer[parser.offset++])
216239
if (response === undefined) {
240+
parser.arrayCache = responses
241+
parser.arrayPos = i
242+
parser.offset = offset
217243
return
218244
}
219245
responses[i] = response
246+
i++
220247
}
221248

222249
return responses
@@ -297,6 +324,8 @@ function JavascriptRedisParser (options) {
297324
this.bigOffset = 0
298325
this.totalChunkSize = 0
299326
this.bufferCache = []
327+
this.arrayCache = null
328+
this.arrayPos = 0
300329
}
301330

302331
/**
@@ -399,6 +428,7 @@ function concatBuffer (parser, length) {
399428
* @returns {undefined}
400429
*/
401430
JavascriptRedisParser.prototype.execute = function execute (buffer) {
431+
var arr
402432
if (this.buffer === null) {
403433
this.buffer = buffer
404434
this.offset = 0
@@ -410,18 +440,34 @@ JavascriptRedisParser.prototype.execute = function execute (buffer) {
410440
buffer.copy(newBuffer, remainingLength, 0, buffer.length)
411441
this.buffer = newBuffer
412442
this.offset = 0
443+
if (this.arrayCache) {
444+
arr = parseArrayChunks(this)
445+
if (!arr) {
446+
return
447+
}
448+
this.returnReply(arr)
449+
this.arrayCache = null
450+
}
413451
} else if (this.totalChunkSize + buffer.length >= this.bigStrSize) {
414452
this.bufferCache.push(buffer)
415-
// The returned type might be Array * (42) and in that case we can't improve the parsing currently
416-
if (this.optionReturnBuffers === false && this.buffer[this.offset] === 36) {
453+
if (this.optionReturnBuffers === false && !this.arrayCache) {
417454
this.returnReply(concatBulkString(this))
418455
this.buffer = buffer
419-
} else { // This applies for arrays too
456+
} else {
420457
this.buffer = concatBuffer(this, this.totalChunkSize + buffer.length)
421458
this.offset = 0
459+
if (this.arrayCache) {
460+
arr = parseArrayChunks(this)
461+
if (!arr) {
462+
this.bigStrSize = 0
463+
this.bufferCache = []
464+
return
465+
}
466+
this.returnReply(arr)
467+
this.arrayCache = null
468+
}
422469
}
423470
this.bigStrSize = 0
424-
this.totalChunkSize = 0
425471
this.bufferCache = []
426472
} else {
427473
this.bufferCache.push(buffer)
@@ -434,7 +480,9 @@ JavascriptRedisParser.prototype.execute = function execute (buffer) {
434480
var type = this.buffer[this.offset++]
435481
var response = parseType(this, type)
436482
if (response === undefined) {
437-
this.offset = offset
483+
if (!this.arrayCache) {
484+
this.offset = offset
485+
}
438486
return
439487
}
440488

0 commit comments

Comments
 (0)