Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 24ec701

Browse filesBrowse files
jasnellMylesBorins
authored andcommitted
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 <trev.norris@gmail.com>
1 parent 36c58da commit 24ec701
Copy full SHA for 24ec701

File tree

Expand file treeCollapse file tree

5 files changed

+248
-27
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

5 files changed

+248
-27
lines changed
Open diff view settings
Collapse file
+58Lines changed: 58 additions & 0 deletions
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+
}
Collapse file

‎doc/api/buffer.markdown‎

Copy file name to clipboardExpand all lines: doc/api/buffer.markdown
+34-7Lines changed: 34 additions & 7 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -657,18 +657,26 @@ console.log(buf.toString('ascii'));
657657
// Prints: Node.js
658658
```
659659

660-
### buf.compare(otherBuffer)
661-
662-
* `otherBuffer` {Buffer}
660+
### buf.compare(target[, targetStart[, targetEnd[, sourceStart[, sourceEnd]]]])
661+
662+
* `target` {Buffer}
663+
* `targetStart` {Integer} The offset within `target` at which to begin
664+
comparison. default = `0`.
665+
* `targetEnd` {Integer} The offset with `target` at which to end comparison.
666+
Ignored when `targetStart` is `undefined`. default = `target.byteLength`.
667+
* `sourceStart` {Integer} The offset within `buf` at which to begin comparison.
668+
Ignored when `targetStart` is `undefined`. default = `0`
669+
* `sourceEnd` {Integer} The offset within `buf` at which to end comparison.
670+
Ignored when `targetStart` is `undefined`. default = `buf.byteLength`.
663671
* Return: {Number}
664672

665673
Compares two Buffer instances and returns a number indicating whether `buf`
666-
comes before, after, or is the same as the `otherBuffer` in sort order.
674+
comes before, after, or is the same as the `target` in sort order.
667675
Comparison is based on the actual sequence of bytes in each Buffer.
668676

669-
* `0` is returned if `otherBuffer` is the same as `buf`
670-
* `1` is returned if `otherBuffer` should come *before* `buf` when sorted.
671-
* `-1` is returned if `otherBuffer` should come *after* `buf` when sorted.
677+
* `0` is returned if `target` is the same as `buf`
678+
* `1` is returned if `target` should come *before* `buf` when sorted.
679+
* `-1` is returned if `target` should come *after* `buf` when sorted.
672680

673681
```js
674682
const buf1 = Buffer.from('ABC');
@@ -690,6 +698,25 @@ console.log(buf2.compare(buf3));
690698
// produces sort order [buf1, buf3, buf2]
691699
```
692700

701+
The optional `targetStart`, `targetEnd`, `sourceStart`, and `sourceEnd`
702+
arguments can be used to limit the comparison to specific ranges within the two
703+
`Buffer` objects.
704+
705+
```js
706+
const buf1 = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8, 9]);
707+
const buf2 = Buffer.from([5, 6, 7, 8, 9, 1, 2, 3, 4]);
708+
709+
console.log(buf1.compare(buf2, 5, 9, 0, 4));
710+
// Prints: 0
711+
console.log(buf1.compare(buf2, 0, 6, 4));
712+
// Prints: -1
713+
console.log(buf1.compare(buf2, 5, 6, 5));
714+
// Prints: 1
715+
```
716+
717+
A `RangeError` will be thrown if: `targetStart < 0`, `sourceStart < 0`,
718+
`targetEnd > target.byteLength` or `sourceEnd > source.byteLength`.
719+
693720
### buf.copy(targetBuffer[, targetStart[, sourceStart[, sourceEnd]]])
694721

695722
* `targetBuffer` {Buffer} Buffer to copy into
Collapse file

‎lib/buffer.js‎

Copy file name to clipboardExpand all lines: lib/buffer.js
+33-4Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -513,15 +513,44 @@ Buffer.prototype.inspect = function inspect() {
513513
return '<' + this.constructor.name + ' ' + str + '>';
514514
};
515515

516+
Buffer.prototype.compare = function compare(target,
517+
start,
518+
end,
519+
thisStart,
520+
thisEnd) {
516521

517-
Buffer.prototype.compare = function compare(b) {
518-
if (!(b instanceof Buffer))
522+
if (!(target instanceof Buffer))
519523
throw new TypeError('Argument must be a Buffer');
520524

521-
if (this === b)
525+
if (start === undefined)
526+
start = 0;
527+
if (end === undefined)
528+
end = target ? target.length : 0;
529+
if (thisStart === undefined)
530+
thisStart = 0;
531+
if (thisEnd === undefined)
532+
thisEnd = this.length;
533+
534+
if (start < 0 ||
535+
end > target.length ||
536+
thisStart < 0 ||
537+
thisEnd > this.length) {
538+
throw new RangeError('out of range index');
539+
}
540+
541+
if (thisStart >= thisEnd && start >= end)
522542
return 0;
543+
if (thisStart >= thisEnd)
544+
return -1;
545+
if (start >= end)
546+
return 1;
547+
548+
start >>>= 0;
549+
end >>>= 0;
550+
thisStart >>>= 0;
551+
thisEnd >>>= 0;
523552

524-
return binding.compare(this, b);
553+
return binding.compareOffset(this, target, start, thisStart, end, thisEnd);
525554
};
526555

527556
function slowIndexOf(buffer, val, byteOffset, encoding) {
Collapse file

‎src/node_buffer.cc‎

Copy file name to clipboardExpand all lines: src/node_buffer.cc
+60-16Lines changed: 60 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -880,6 +880,62 @@ void ByteLengthUtf8(const FunctionCallbackInfo<Value> &args) {
880880
args.GetReturnValue().Set(args[0].As<String>()->Utf8Length());
881881
}
882882

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

884940
void Compare(const FunctionCallbackInfo<Value> &args) {
885941
Environment* env = Environment::GetCurrent(args);
@@ -891,22 +947,9 @@ void Compare(const FunctionCallbackInfo<Value> &args) {
891947

892948
size_t cmp_length = MIN(obj_a_length, obj_b_length);
893949

894-
int val = cmp_length > 0 ? memcmp(obj_a_data, obj_b_data, cmp_length) : 0;
895-
896-
// Normalize val to be an integer in the range of [1, -1] since
897-
// implementations of memcmp() can vary by platform.
898-
if (val == 0) {
899-
if (obj_a_length > obj_b_length)
900-
val = 1;
901-
else if (obj_a_length < obj_b_length)
902-
val = -1;
903-
} else {
904-
if (val > 0)
905-
val = 1;
906-
else
907-
val = -1;
908-
}
909-
950+
int val = normalizeCompareVal(cmp_length > 0 ?
951+
memcmp(obj_a_data, obj_b_data, cmp_length) : 0,
952+
obj_a_length, obj_b_length);
910953
args.GetReturnValue().Set(val);
911954
}
912955

@@ -1182,6 +1225,7 @@ void Initialize(Local<Object> target,
11821225

11831226
env->SetMethod(target, "byteLengthUtf8", ByteLengthUtf8);
11841227
env->SetMethod(target, "compare", Compare);
1228+
env->SetMethod(target, "compareOffset", CompareOffset);
11851229
env->SetMethod(target, "fill", Fill);
11861230
env->SetMethod(target, "indexOfBuffer", IndexOfBuffer);
11871231
env->SetMethod(target, "indexOfNumber", IndexOfNumber);
Collapse file
+63Lines changed: 63 additions & 0 deletions
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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.