Replace cachedContains with createCache and further optimize linear array searches.

Former-commit-id: bfe905985c9125cbadfcf111ffd97b6f8ecdd58d
This commit is contained in:
John-David Dalton
2013-05-19 19:21:11 -07:00
parent e7bad10663
commit 010c26e716
2 changed files with 122 additions and 42 deletions

112
lodash.js
View File

@@ -33,7 +33,7 @@
var keyPrefix = +new Date + ''; var keyPrefix = +new Date + '';
/** Used as the size when optimizations are enabled for large arrays */ /** Used as the size when optimizations are enabled for large arrays */
var largeArraySize = 200; var largeArraySize = 75;
/** Used to match empty string literals in compiled template source */ /** Used to match empty string literals in compiled template source */
var reEmptyStringLeading = /\b__p \+= '';/g, var reEmptyStringLeading = /\b__p \+= '';/g,
@@ -662,26 +662,72 @@
* @param {Mixed} value The value to search for. * @param {Mixed} value The value to search for.
* @returns {Boolean} Returns `true`, if `value` is found, else `false`. * @returns {Boolean} Returns `true`, if `value` is found, else `false`.
*/ */
function cachedContains(array) { function createCache(array) {
var length = array.length, var bailout,
isLarge = length >= largeArraySize; index = -1,
length = array.length,
isLarge = length >= largeArraySize,
objCache = {};
if (isLarge) { var caches = {
var cache = {}, 'false': false,
index = -1; 'function': false,
'null': false,
'number': {},
'object': objCache,
'string': {},
'true': false,
'undefined': false
};
while (++index < length) { function cacheContains(value) {
var key = keyPrefix + array[index]; var type = typeof value;
(cache[key] || (cache[key] = [])).push(array[index]); if (type == 'boolean' || value == null) {
return caches[value];
}
var cache = caches[type] || (type = 'object', objCache),
key = type == 'number' ? value : keyPrefix + value;
return type == 'object'
? (cache[key] ? indexOf(cache[key], value) > -1 : false)
: !!cache[key];
}
function cachePush(value) {
var type = typeof value;
if (type == 'boolean' || value == null) {
caches[value] = true;
} else {
var cache = caches[type] || (type = 'object', objCache),
key = type == 'number' ? value : keyPrefix + value;
if (type == 'object') {
bailout = (cache[key] || (cache[key] = [])).push(value) == length;
} else {
cache[key] = true;
}
} }
} }
return function(value) {
if (isLarge) { function simpleContains(value) {
var key = keyPrefix + value;
return cache[key] && indexOf(cache[key], value) > -1;
}
return indexOf(array, value) > -1; return indexOf(array, value) > -1;
} }
function simplePush(value) {
array.push(value);
}
if (isLarge) {
while (++index < length) {
cachePush(array[index]);
}
if (bailout) {
isLarge = caches = objCache = null;
}
}
return isLarge
? { 'contains': cacheContains, 'push': cachePush }
: { 'push': simplePush, 'contains' : simpleContains };
} }
/** /**
@@ -3441,7 +3487,7 @@
var index = -1, var index = -1,
length = array ? array.length : 0, length = array ? array.length : 0,
flattened = concat.apply(arrayProto, nativeSlice.call(arguments, 1)), flattened = concat.apply(arrayProto, nativeSlice.call(arguments, 1)),
contains = cachedContains(flattened), contains = createCache(flattened).contains,
result = []; result = [];
while (++index < length) { while (++index < length) {
@@ -3769,29 +3815,21 @@
function intersection(array) { function intersection(array) {
var args = arguments, var args = arguments,
argsLength = args.length, argsLength = args.length,
cache = { '0': {} }, cache = createCache([]),
caches = {},
index = -1, index = -1,
length = array ? array.length : 0, length = array ? array.length : 0,
isLarge = length >= largeArraySize, isLarge = length >= largeArraySize,
result = [], result = [];
seen = result;
outer: outer:
while (++index < length) { while (++index < length) {
var value = array[index]; var value = array[index];
if (isLarge) { if (!cache.contains(value)) {
var key = keyPrefix + value;
var inited = cache[0][key]
? !(seen = cache[0][key])
: (seen = cache[0][key] = []);
}
if (inited || indexOf(seen, value) < 0) {
if (isLarge) {
seen.push(value);
}
var argsIndex = argsLength; var argsIndex = argsLength;
cache.push(value);
while (--argsIndex) { while (--argsIndex) {
if (!(cache[argsIndex] || (cache[argsIndex] = cachedContains(args[argsIndex])))(value)) { if (!(caches[argsIndex] || (caches[argsIndex] = createCache(args[argsIndex]).contains))(value)) {
continue outer; continue outer;
} }
} }
@@ -4179,26 +4217,20 @@
} }
// init value cache for large arrays // init value cache for large arrays
var isLarge = !isSorted && length >= largeArraySize; var isLarge = !isSorted && length >= largeArraySize;
if (isLarge) {
var cache = {};
}
if (callback != null) { if (callback != null) {
seen = []; seen = [];
callback = lodash.createCallback(callback, thisArg); callback = lodash.createCallback(callback, thisArg);
} }
if (isLarge) {
seen = createCache([]);
}
while (++index < length) { while (++index < length) {
var value = array[index], var value = array[index],
computed = callback ? callback(value, index, array) : value; computed = callback ? callback(value, index, array) : value;
if (isLarge) {
var key = keyPrefix + computed;
var inited = cache[key]
? !(seen = cache[key])
: (seen = cache[key] = []);
}
if (isSorted if (isSorted
? !index || seen[seen.length - 1] !== computed ? !index || seen[seen.length - 1] !== computed
: inited || indexOf(seen, computed) < 0 : (isLarge ? !seen.contains(computed) : indexOf(seen, computed) < 0)
) { ) {
if (callback || isLarge) { if (callback || isLarge) {
seen.push(computed); seen.push(computed);

View File

@@ -775,6 +775,18 @@
) )
); );
suites.push(
Benchmark.Suite('`_.difference` iterating 75 elements')
.add(buildName, {
'fn': 'lodash.difference(seventyFiveValues, seventyFiveValues2)',
'teardown': 'function multiArrays(){}'
})
.add(otherName, {
'fn': '_.difference(seventyFiveValues, seventyFiveValues2)',
'teardown': 'function multiArrays(){}'
})
);
suites.push( suites.push(
Benchmark.Suite('`_.difference` iterating 200 elements') Benchmark.Suite('`_.difference` iterating 200 elements')
.add(buildName, { .add(buildName, {
@@ -1076,6 +1088,18 @@
) )
); );
suites.push(
Benchmark.Suite('`_.intersection` iterating 75 elements')
.add(buildName, {
'fn': 'lodash.intersection(seventyFiveValues, seventyFiveValues2)',
'teardown': 'function multiArrays(){}'
})
.add(otherName, {
'fn': '_.intersection(seventyFiveValues, seventyFiveValues2)',
'teardown': 'function multiArrays(){}'
})
);
suites.push( suites.push(
Benchmark.Suite('`_.intersection` iterating 200 elements') Benchmark.Suite('`_.intersection` iterating 200 elements')
.add(buildName, { .add(buildName, {
@@ -1731,11 +1755,23 @@
suites.push( suites.push(
Benchmark.Suite('`_.union` iterating an array of 75 elements') Benchmark.Suite('`_.union` iterating an array of 75 elements')
.add(buildName, { .add(buildName, {
'fn': 'lodash.union(fiftyValues, twentyFiveValues2)', 'fn': 'lodash.union(twentyFiveValues, fiftyValues2)',
'teardown': 'function multiArrays(){}' 'teardown': 'function multiArrays(){}'
}) })
.add(otherName, { .add(otherName, {
'fn': '_.union(fiftyValues, twentyFiveValues2)', 'fn': '_.union(twentyFiveValues, fiftyValues2)',
'teardown': 'function multiArrays(){}'
})
);
suites.push(
Benchmark.Suite('`_.union` iterating an array of 200 elements')
.add(buildName, {
'fn': 'lodash.union(oneHundredValues, oneHundredValues2)',
'teardown': 'function multiArrays(){}'
})
.add(otherName, {
'fn': '_.union(oneHundredValues, oneHundredValues2)',
'teardown': 'function multiArrays(){}' 'teardown': 'function multiArrays(){}'
}) })
); );
@@ -1766,6 +1802,18 @@
) )
); );
suites.push(
Benchmark.Suite('`_.uniq` iterating an array of 75 elements')
.add(buildName, {
'fn': 'lodash.uniq(twentyFiveValues.concat(fiftyValues2))',
'teardown': 'function multiArrays(){}'
})
.add(otherName, {
'fn': '_.uniq(twentyFiveValues.concat(fiftyValues2))',
'teardown': 'function multiArrays(){}'
})
);
suites.push( suites.push(
Benchmark.Suite('`_.uniq` iterating an array of 200 elements') Benchmark.Suite('`_.uniq` iterating an array of 200 elements')
.add(buildName, { .add(buildName, {