Add optimizations for large arrays to _.difference, _.intersection, and _.without.

Former-commit-id: 26d55a6a3340e77b5269b2003d20def3fe77bca9
This commit is contained in:
John-David Dalton
2012-07-15 03:51:28 -04:00
parent e58d47a3b2
commit 8577816234
3 changed files with 311 additions and 12 deletions

View File

@@ -439,6 +439,41 @@
/*--------------------------------------------------------------------------*/
/**
* Creates a new function optimized for searching large arrays for a given `value`,
* starting at `fromIndex`, using strict equality for comparisons, i.e. `===`.
*
* @private
* @param {Array} array The array to search.
* @param {Mixed} value The value to search for.
* @param {Number} [fromIndex=0] The index to start searching from.
* @param {Number} [largeSize=30] The length at which an array is considered large.
* @returns {Boolean} Returns `true` if `value` is found, else `false`.
*/
function cachedContains(array, fromIndex, largeSize) {
fromIndex || (fromIndex = 0);
var length = array.length,
isLarge = (length - fromIndex) >= (largeSize || 30),
cache = isLarge ? {} : array;
if (isLarge) {
// init value cache
var value,
index = fromIndex - 1;
while (++index < length) {
value = array[index];
(hasOwnProperty.call(cache, value) ? cache[value] : (cache[value] = [])).push(value);
}
}
return function(value) {
return isLarge
? hasOwnProperty.call(cache, value) && indexOf(cache[value], value) > -1
: indexOf(cache, value, fromIndex) > -1;
}
}
/**
* Creates compiled iteration functions. The iteration function will be created
* to iterate over only objects if the first argument of `options.args` is
@@ -1224,10 +1259,11 @@
}
var index = -1,
length = array.length,
flattened = concat.apply(result, arguments);
flattened = concat.apply(result, arguments),
contains = cachedContains(flattened, length);
while (++index < length) {
if (indexOf(flattened, array[index], length) < 0) {
if (!contains(array[index])) {
result.push(array[index]);
}
}
@@ -1390,12 +1426,15 @@
var value,
index = -1,
length = array.length,
others = slice.call(arguments, 1);
others = slice.call(arguments, 1),
cache = [];
while (++index < length) {
value = array[index];
if (indexOf(result, value) < 0 &&
every(others, function(other) { return indexOf(other, value) > -1; })) {
every(others, function(other, index) {
return (cache[index] || (cache[index] = cachedContains(other)))(value);
})) {
result.push(value);
}
}
@@ -1847,10 +1886,11 @@
return result;
}
var index = -1,
length = array.length;
length = array.length,
contains = cachedContains(arguments, 1, 20);
while (++index < length) {
if (indexOf(arguments, array[index], 1) < 0) {
if (!contains(array[index])) {
result.push(array[index]);
}
}
@@ -2774,7 +2814,8 @@
}
}
}
} else {
}
else {
// objects with different constructors are not equivalent
if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) {
return false;

View File

@@ -95,7 +95,6 @@
belt = this.name == 'Lo-Dash' ? lodash : _;
var index,
length,
limit = 20,
object = {},
objects = Array(limit),
@@ -104,7 +103,7 @@
nestedNumbers = [1, [2], [3, [[4]]]],
twoNumbers = [12, 21];
for (index = 0, length = limit; index < length; index++) {
for (index = 0; index < limit; index++) {
numbers[index] = index;
object['key' + index] = index;
objects[index] = { 'num': index };
@@ -132,7 +131,7 @@
funcNames = belt.functions(lodash);
// potentially expensive
for (index = 0, length = this.count; index < length; index++) {
for (index = 0; index < this.count; index++) {
bindAllObjects[index] = belt.clone(lodash);
}
}
@@ -187,13 +186,50 @@
numbers2 = Array(limit),
nestedNumbers2 = [1, [2], [3, [[4]]]];
for (index = 0, length = limit; index < length; index++) {
for (index = 0; index < limit; index++) {
numbers2[index] = index;
object2['key' + index] = index;
objects2[index] = { 'num': index };
}
}
if (typeof multiArrays != 'undefined') {
var twentyFiveValues = Array(25),
twentyFiveValues2 = Array(25),
fiftyValues = Array(50),
fiftyValues2 = Array(50),
seventyFiveValues = Array(75),
seventyFiveValues2 = Array(75),
lowerChars = 'abcdefghijklmnopqrstuvwxyz'.split(''),
upperChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
for (index = 0; index < 75; index++) {
if (index < 26) {
if (index < 20) {
twentyFiveValues[index] = lowerChars[index];
twentyFiveValues2[index] = upperChars[index];
}
else if (index < 25) {
twentyFiveValues[index] =
twentyFiveValues2[index] = index;
}
fiftyValues[index] =
seventyFiveValues[index] = lowerChars[index];
fiftyValues2[index] =
seventyFiveValues2[index] = upperChars[index];
}
else {
if (index < 50) {
fiftyValues[index] = index;
fiftyValues2[index] = index + (index < 40 ? 75 : 0);
}
seventyFiveValues[index] = index;
seventyFiveValues2[index] = index + (index < 60 ? 75 : 0);
}
}
}
if (typeof template != 'undefined') {
var tplData = {
'header1': 'Header1',
@@ -489,6 +525,54 @@
})
);
suites.push(
Benchmark.Suite('`_.difference` iterating 25 elements')
.add('Lo-Dash', {
'fn': function() {
lodash.difference(twentyFiveValues, twentyFiveValues2);
},
'teardown': 'function multiArrays(){}'
})
.add('Underscore', {
'fn': function() {
_.difference(twentyFiveValues, twentyFiveValues2);
},
'teardown': 'function multiArrays(){}'
})
);
suites.push(
Benchmark.Suite('`_.difference` iterating 50 elements')
.add('Lo-Dash', {
'fn': function() {
lodash.difference(fiftyValues, fiftyValues2);
},
'teardown': 'function multiArrays(){}'
})
.add('Underscore', {
'fn': function() {
_.difference(fiftyValues, fiftyValues2);
},
'teardown': 'function multiArrays(){}'
})
);
suites.push(
Benchmark.Suite('`_.difference` iterating 75 elements')
.add('Lo-Dash', {
'fn': function() {
lodash.difference(seventyFiveValues, seventyFiveValues2);
},
'teardown': 'function multiArrays(){}'
})
.add('Underscore', {
'fn': function() {
_.difference(seventyFiveValues, seventyFiveValues2);
},
'teardown': 'function multiArrays(){}'
})
);
/*--------------------------------------------------------------------------*/
suites.push(
@@ -725,6 +809,54 @@
})
);
suites.push(
Benchmark.Suite('`_.intersection` iterating 25 elements')
.add('Lo-Dash', {
'fn': function() {
lodash.intersection(twentyFiveValues, twentyFiveValues2);
},
'teardown': 'function multiArrays(){}'
})
.add('Underscore', {
'fn': function() {
_.intersection(twentyFiveValues, twentyFiveValues2);
},
'teardown': 'function multiArrays(){}'
})
);
suites.push(
Benchmark.Suite('`_.intersection` iterating 50 elements')
.add('Lo-Dash', {
'fn': function() {
lodash.intersection(fiftyValues, fiftyValues2);
},
'teardown': 'function multiArrays(){}'
})
.add('Underscore', {
'fn': function() {
_.intersection(fiftyValues, fiftyValues2);
},
'teardown': 'function multiArrays(){}'
})
);
suites.push(
Benchmark.Suite('`_.intersection` iterating 75 elements')
.add('Lo-Dash', {
'fn': function() {
lodash.intersection(seventyFiveValues, seventyFiveValues2);
},
'teardown': 'function multiArrays(){}'
})
.add('Underscore', {
'fn': function() {
_.intersection(seventyFiveValues, seventyFiveValues2);
},
'teardown': 'function multiArrays(){}'
})
);
/*--------------------------------------------------------------------------*/
suites.push(
@@ -759,7 +891,6 @@
/*--------------------------------------------------------------------------*/
suites.push(
Benchmark.Suite('`_.isEqual` comparing primitives and objects (edge case)')
.add('Lo-Dash', {
@@ -1218,6 +1349,54 @@
})
);
suites.push(
Benchmark.Suite('`_.union` iterating an array of 25 elements')
.add('Lo-Dash', {
'fn': function() {
lodash.union(twentyFiveValues, twentyFiveValues2);
},
'teardown': 'function multiArrays(){}'
})
.add('Underscore', {
'fn': function() {
_.union(twentyFiveValues, twentyFiveValues2);
},
'teardown': 'function multiArrays(){}'
})
);
suites.push(
Benchmark.Suite('`_.union` iterating an array of 50 elements')
.add('Lo-Dash', {
'fn': function() {
lodash.union(fiftyValues, fiftyValues2);
},
'teardown': 'function multiArrays(){}'
})
.add('Underscore', {
'fn': function() {
_.union(fiftyValues, fiftyValues2);
},
'teardown': 'function multiArrays(){}'
})
);
suites.push(
Benchmark.Suite('`_.union` iterating an array of 75 elements')
.add('Lo-Dash', {
'fn': function() {
lodash.union(seventyFiveValues, seventyFiveValues2);
},
'teardown': 'function multiArrays(){}'
})
.add('Underscore', {
'fn': function() {
_.union(seventyFiveValues, seventyFiveValues2);
},
'teardown': 'function multiArrays(){}'
})
);
/*--------------------------------------------------------------------------*/
suites.push(
@@ -1258,6 +1437,66 @@
/*--------------------------------------------------------------------------*/
suites.push(
Benchmark.Suite('`_.without`')
.add('Lo-Dash', function() {
lodash.without(numbers, 9, 12, 14, 15);
})
.add('Underscore', function() {
_.without(numbers, 9, 12, 14, 15);
})
);
suites.push(
Benchmark.Suite('`_.without` iterating an array of 25 elements')
.add('Lo-Dash', {
'fn': function() {
lodash.without.apply(lodash, [twentyFiveValues].concat(twentyFiveValues2));
},
'teardown': 'function multiArrays(){}'
})
.add('Underscore', {
'fn': function() {
_.without.apply(_, [twentyFiveValues].concat(twentyFiveValues2));
},
'teardown': 'function multiArrays(){}'
})
);
suites.push(
Benchmark.Suite('`_.without` iterating an array of 50 elements')
.add('Lo-Dash', {
'fn': function() {
lodash.without.apply(lodash, [fiftyValues].concat(fiftyValues2));
},
'teardown': 'function multiArrays(){}'
})
.add('Underscore', {
'fn': function() {
_.without.apply(_, [fiftyValues].concat(fiftyValues2));
},
'teardown': 'function multiArrays(){}'
})
);
suites.push(
Benchmark.Suite('`_.without` iterating an array of 75 elements')
.add('Lo-Dash', {
'fn': function() {
lodash.without.apply(lodash, [seventyFiveValues].concat(seventyFiveValues2));
},
'teardown': 'function multiArrays(){}'
})
.add('Underscore', {
'fn': function() {
_.without.apply(_, [seventyFiveValues].concat(seventyFiveValues2));
},
'teardown': 'function multiArrays(){}'
})
);
/*--------------------------------------------------------------------------*/
if (Benchmark.platform + '') {
log(Benchmark.platform + '');
}

View File

@@ -166,6 +166,25 @@
/*--------------------------------------------------------------------------*/
QUnit.module('lodash.difference');
(function() {
test('should work correctly when using `cachedContains`', function() {
var array1 = _.range(27),
array2 = array1.slice(),
a = {},
b = {},
c = {};
array1.push(a, b, c);
array2.push(b, c, a);
deepEqual(_.difference(array1, array2), []);
});
}());
/*--------------------------------------------------------------------------*/
QUnit.module('lodash.escape');
(function() {