Skip to content

Commit 6832078

Browse files
committedApr 26, 2016
buffer: add Buffer.prototype.compare by offset
Adds additional `targetStart`, `targetEnd`, `sourceStart, and `sourceEnd` arguments to `Buffer.prototype.compare` to allow comparison of sub-ranges of two Buffers without requiring Buffer.prototype.slice() Fixes: #521 PR-URL: #5880 Reviewed-By: Trevor Norris <[email protected]>
1 parent dd4b0e5 commit 6832078

File tree

5 files changed

+248
-27
lines changed

5 files changed

+248
-27
lines changed
 
+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
'use strict';
2+
const common = require('../common.js');
3+
const v8 = require('v8');
4+
5+
const bench = common.createBenchmark(main, {
6+
method: ['offset', 'slice'],
7+
size: [16, 512, 1024, 4096, 16386],
8+
millions: [1]
9+
});
10+
11+
function compareUsingSlice(b0, b1, len, iter) {
12+
13+
// Force optimization before starting the benchmark
14+
Buffer.compare(b0.slice(1, len), b1.slice(1, len));
15+
v8.setFlagsFromString('--allow_natives_syntax');
16+
eval('%OptimizeFunctionOnNextCall(Buffer.compare)');
17+
eval('%OptimizeFunctionOnNextCall(b0.slice)');
18+
eval('%OptimizeFunctionOnNextCall(b1.slice)');
19+
Buffer.compare(b0.slice(1, len), b1.slice(1, len));
20+
doCompareUsingSlice(b0, b1, len, iter);
21+
}
22+
23+
function doCompareUsingSlice(b0, b1, len, iter) {
24+
var i;
25+
bench.start();
26+
for (i = 0; i < iter; i++)
27+
Buffer.compare(b0.slice(1, len), b1.slice(1, len));
28+
bench.end(iter / 1e6);
29+
}
30+
31+
function compareUsingOffset(b0, b1, len, iter) {
32+
len = len + 1;
33+
// Force optimization before starting the benchmark
34+
b0.compare(b1, 1, len, 1, len);
35+
v8.setFlagsFromString('--allow_natives_syntax');
36+
eval('%OptimizeFunctionOnNextCall(b0.compare)');
37+
b0.compare(b1, 1, len, 1, len);
38+
doCompareUsingOffset(b0, b1, len, iter);
39+
}
40+
41+
function doCompareUsingOffset(b0, b1, len, iter) {
42+
var i;
43+
bench.start();
44+
for (i = 0; i < iter; i++)
45+
b0.compare(b1, 1, len, 1, len);
46+
bench.end(iter / 1e6);
47+
}
48+
49+
function main(conf) {
50+
const iter = (conf.millions >>> 0) * 1e6;
51+
const size = (conf.size >>> 0);
52+
const method = conf.method === 'slice' ?
53+
compareUsingSlice : compareUsingOffset;
54+
method(Buffer.alloc(size, 'a'),
55+
Buffer.alloc(size, 'b'),
56+
size >> 1,
57+
iter);
58+
}

‎doc/api/buffer.markdown

+34-7
Original file line numberDiff line numberDiff line change
@@ -675,18 +675,26 @@ console.log(buf.toString('ascii'));
675675
// Prints: Node.js
676676
```
677677

678-
### buf.compare(otherBuffer)
679-
680-
* `otherBuffer` {Buffer}
678+
### buf.compare(target[, targetStart[, targetEnd[, sourceStart[, sourceEnd]]]])
679+
680+
* `target` {Buffer}
681+
* `targetStart` {Integer} The offset within `target` at which to begin
682+
comparison. default = `0`.
683+
* `targetEnd` {Integer} The offset with `target` at which to end comparison.
684+
Ignored when `targetStart` is `undefined`. default = `target.byteLength`.
685+
* `sourceStart` {Integer} The offset within `buf` at which to begin comparison.
686+
Ignored when `targetStart` is `undefined`. default = `0`
687+
* `sourceEnd` {Integer} The offset within `buf` at which to end comparison.
688+
Ignored when `targetStart` is `undefined`. default = `buf.byteLength`.
681689
* Return: {Number}
682690

683691
Compares two Buffer instances and returns a number indicating whether `buf`
684-
comes before, after, or is the same as the `otherBuffer` in sort order.
692+
comes before, after, or is the same as the `target` in sort order.
685693
Comparison is based on the actual sequence of bytes in each Buffer.
686694

687-
* `0` is returned if `otherBuffer` is the same as `buf`
688-
* `1` is returned if `otherBuffer` should come *before* `buf` when sorted.
689-
* `-1` is returned if `otherBuffer` should come *after* `buf` when sorted.
695+
* `0` is returned if `target` is the same as `buf`
696+
* `1` is returned if `target` should come *before* `buf` when sorted.
697+
* `-1` is returned if `target` should come *after* `buf` when sorted.
690698

691699
```js
692700
const buf1 = Buffer.from('ABC');
@@ -708,6 +716,25 @@ console.log(buf2.compare(buf3));
708716
// produces sort order [buf1, buf3, buf2]
709717
```
710718

719+
The optional `targetStart`, `targetEnd`, `sourceStart`, and `sourceEnd`
720+
arguments can be used to limit the comparison to specific ranges within the two
721+
`Buffer` objects.
722+
723+
```js
724+
const buf1 = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8, 9]);
725+
const buf2 = Buffer.from([5, 6, 7, 8, 9, 1, 2, 3, 4]);
726+
727+
console.log(buf1.compare(buf2, 5, 9, 0, 4));
728+
// Prints: 0
729+
console.log(buf1.compare(buf2, 0, 6, 4));
730+
// Prints: -1
731+
console.log(buf1.compare(buf2, 5, 6, 5));
732+
// Prints: 1
733+
```
734+
735+
A `RangeError` will be thrown if: `targetStart < 0`, `sourceStart < 0`,
736+
`targetEnd > target.byteLength` or `sourceEnd > source.byteLength`.
737+
711738
### buf.copy(targetBuffer[, targetStart[, sourceStart[, sourceEnd]]])
712739

713740
* `targetBuffer` {Buffer} Buffer to copy into

‎lib/buffer.js

+33-4
Original file line numberDiff line numberDiff line change
@@ -537,15 +537,44 @@ Buffer.prototype.inspect = function inspect() {
537537
return '<' + this.constructor.name + ' ' + str + '>';
538538
};
539539

540+
Buffer.prototype.compare = function compare(target,
541+
start,
542+
end,
543+
thisStart,
544+
thisEnd) {
540545

541-
Buffer.prototype.compare = function compare(b) {
542-
if (!(b instanceof Buffer))
546+
if (!(target instanceof Buffer))
543547
throw new TypeError('Argument must be a Buffer');
544548

545-
if (this === b)
549+
if (start === undefined)
550+
start = 0;
551+
if (end === undefined)
552+
end = target ? target.length : 0;
553+
if (thisStart === undefined)
554+
thisStart = 0;
555+
if (thisEnd === undefined)
556+
thisEnd = this.length;
557+
558+
if (start < 0 ||
559+
end > target.length ||
560+
thisStart < 0 ||
561+
thisEnd > this.length) {
562+
throw new RangeError('out of range index');
563+
}
564+
565+
if (thisStart >= thisEnd && start >= end)
546566
return 0;
567+
if (thisStart >= thisEnd)
568+
return -1;
569+
if (start >= end)
570+
return 1;
571+
572+
start >>>= 0;
573+
end >>>= 0;
574+
thisStart >>>= 0;
575+
thisEnd >>>= 0;
547576

548-
return binding.compare(this, b);
577+
return binding.compareOffset(this, target, start, thisStart, end, thisEnd);
549578
};
550579

551580
function slowIndexOf(buffer, val, byteOffset, encoding) {

‎src/node_buffer.cc

+60-16
Original file line numberDiff line numberDiff line change
@@ -870,6 +870,62 @@ void ByteLengthUtf8(const FunctionCallbackInfo<Value> &args) {
870870
args.GetReturnValue().Set(args[0].As<String>()->Utf8Length());
871871
}
872872

873+
// Normalize val to be an integer in the range of [1, -1] since
874+
// implementations of memcmp() can vary by platform.
875+
static int normalizeCompareVal(int val, size_t a_length, size_t b_length) {
876+
if (val == 0) {
877+
if (a_length > b_length)
878+
return 1;
879+
else if (a_length < b_length)
880+
return -1;
881+
} else {
882+
if (val > 0)
883+
return 1;
884+
else
885+
return -1;
886+
}
887+
return val;
888+
}
889+
890+
void CompareOffset(const FunctionCallbackInfo<Value> &args) {
891+
Environment* env = Environment::GetCurrent(args);
892+
893+
THROW_AND_RETURN_UNLESS_BUFFER(env, args[0]);
894+
THROW_AND_RETURN_UNLESS_BUFFER(env, args[1]);
895+
SPREAD_ARG(args[0], ts_obj);
896+
SPREAD_ARG(args[1], target);
897+
898+
size_t target_start;
899+
size_t source_start;
900+
size_t source_end;
901+
size_t target_end;
902+
903+
CHECK_NOT_OOB(ParseArrayIndex(args[2], 0, &target_start));
904+
CHECK_NOT_OOB(ParseArrayIndex(args[3], 0, &source_start));
905+
CHECK_NOT_OOB(ParseArrayIndex(args[4], target_length, &target_end));
906+
CHECK_NOT_OOB(ParseArrayIndex(args[5], ts_obj_length, &source_end));
907+
908+
if (source_start > ts_obj_length)
909+
return env->ThrowRangeError("out of range index");
910+
if (target_start > target_length)
911+
return env->ThrowRangeError("out of range index");
912+
913+
CHECK_LE(source_start, source_end);
914+
CHECK_LE(target_start, target_end);
915+
916+
size_t to_cmp = MIN(MIN(source_end - source_start,
917+
target_end - target_start),
918+
ts_obj_length - source_start);
919+
920+
int val = normalizeCompareVal(to_cmp > 0 ?
921+
memcmp(ts_obj_data + source_start,
922+
target_data + target_start,
923+
to_cmp) : 0,
924+
source_end - source_start,
925+
target_end - target_start);
926+
927+
args.GetReturnValue().Set(val);
928+
}
873929

874930
void Compare(const FunctionCallbackInfo<Value> &args) {
875931
Environment* env = Environment::GetCurrent(args);
@@ -881,22 +937,9 @@ void Compare(const FunctionCallbackInfo<Value> &args) {
881937

882938
size_t cmp_length = MIN(obj_a_length, obj_b_length);
883939

884-
int val = cmp_length > 0 ? memcmp(obj_a_data, obj_b_data, cmp_length) : 0;
885-
886-
// Normalize val to be an integer in the range of [1, -1] since
887-
// implementations of memcmp() can vary by platform.
888-
if (val == 0) {
889-
if (obj_a_length > obj_b_length)
890-
val = 1;
891-
else if (obj_a_length < obj_b_length)
892-
val = -1;
893-
} else {
894-
if (val > 0)
895-
val = 1;
896-
else
897-
val = -1;
898-
}
899-
940+
int val = normalizeCompareVal(cmp_length > 0 ?
941+
memcmp(obj_a_data, obj_b_data, cmp_length) : 0,
942+
obj_a_length, obj_b_length);
900943
args.GetReturnValue().Set(val);
901944
}
902945

@@ -1172,6 +1215,7 @@ void Initialize(Local<Object> target,
11721215

11731216
env->SetMethod(target, "byteLengthUtf8", ByteLengthUtf8);
11741217
env->SetMethod(target, "compare", Compare);
1218+
env->SetMethod(target, "compareOffset", CompareOffset);
11751219
env->SetMethod(target, "fill", Fill);
11761220
env->SetMethod(target, "indexOfBuffer", IndexOfBuffer);
11771221
env->SetMethod(target, "indexOfNumber", IndexOfNumber);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
'use strict';
2+
3+
require('../common');
4+
const assert = require('assert');
5+
6+
const a = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]);
7+
const b = Buffer.from([5, 6, 7, 8, 9, 0, 1, 2, 3, 4]);
8+
9+
assert.equal(-1, a.compare(b));
10+
11+
// Equivalent to a.compare(b).
12+
assert.equal(-1, a.compare(b, 0));
13+
assert.equal(-1, a.compare(b, '0'));
14+
15+
// Equivalent to a.compare(b).
16+
assert.equal(-1, a.compare(b, 0, undefined, 0));
17+
18+
// Zero-length targer, return 1
19+
assert.equal(1, a.compare(b, 0, 0, 0));
20+
assert.equal(1, a.compare(b, '0', '0', '0'));
21+
22+
// Equivalent to Buffer.compare(a, b.slice(6, 10))
23+
assert.equal(1, a.compare(b, 6, 10));
24+
25+
// Zero-length source, return -1
26+
assert.equal(-1, a.compare(b, 6, 10, 0, 0));
27+
28+
// Equivalent to Buffer.compare(a.slice(4), b.slice(0, 5))
29+
assert.equal(1, a.compare(b, 0, 5, 4));
30+
31+
// Equivalent to Buffer.compare(a.slice(1), b.slice(5))
32+
assert.equal(1, a.compare(b, 5, undefined, 1));
33+
34+
// Equivalent to Buffer.compare(a.slice(2), b.slice(2, 4))
35+
assert.equal(-1, a.compare(b, 2, 4, 2));
36+
37+
// Equivalent to Buffer.compare(a.slice(4), b.slice(0, 7))
38+
assert.equal(-1, a.compare(b, 0, 7, 4));
39+
40+
// Equivalent to Buffer.compare(a.slice(4, 6), b.slice(0, 7));
41+
assert.equal(-1, a.compare(b, 0, 7, 4, 6));
42+
43+
// zero length target
44+
assert.equal(1, a.compare(b, 0, null));
45+
46+
// coerces to targetEnd == 5
47+
assert.equal(-1, a.compare(b, 0, {valueOf: () => 5}));
48+
49+
// zero length target
50+
assert.equal(1, a.compare(b, Infinity, -Infinity));
51+
52+
// zero length target because default for targetEnd <= targetSource
53+
assert.equal(1, a.compare(b, '0xff'));
54+
55+
const oor = /out of range index/;
56+
57+
assert.throws(() => a.compare(b, 0, 100, 0), oor);
58+
assert.throws(() => a.compare(b, 0, 1, 0, 100), oor);
59+
assert.throws(() => a.compare(b, -1), oor);
60+
assert.throws(() => a.compare(b, 0, '0xff'), oor);
61+
assert.throws(() => a.compare(b, 0, Infinity), oor);
62+
assert.throws(() => a.compare(b, -Infinity, Infinity), oor);
63+
assert.throws(() => a.compare(), /Argument must be a Buffer/);

0 commit comments

Comments
 (0)
Please sign in to comment.