Skip to main content

lodash some 方法性能为什么比 js 原生方法 还高

今天写一个性能敏感的函数发现的这个有趣结果,lodash some 的性能是 js some 性能的几倍。

测试代码

const testArr = new Array(50_000_000).fill({ a: 1, b: 2, c: 3 });

console.time("es");
const x = testArr.some((v) => v.a === 9 && v.b === 9 && v.c === 9);
console.timeEnd("es");

console.time("lodash");
const y = _.some(testArr, (v) => v.a === 9 && v.b === 9 && v.c === 9);
console.timeEnd("lodash");
// es: 590.248046875 ms
// lodash: 219.496826171875 ms

可以在 https://lodash.com/ 的 F12 中直接测试,我在 node16 环境下结果也一致,lodash-some 性能是 js-some 的几倍

js some 按我理解 js RunTime 应该是更高性能语言的实现(如 C 等),那么原生 some 方法性能应该更高呀。

[].some --> ƒ some() { [native code] } lodash some lodash 的 some 源码在这 https://github.com/lodash/lodash/blob/master/some.js,也仅仅是很普通的 while 遍历,不知道为啥性能这么好。

v8 源码。

// Executes the function once for each element present in the
// array until it finds one where callback returns true.
function ArraySome(f, receiver) {
CHECK_OBJECT_COERCIBLE(this, "Array.prototype.some");
// Pull out the length so that modifications to the length in the
// loop will not affect the looping and side effects are visible.
var array = ToObject(this);
var length = TO_UINT32(array.length);
if (!IS_SPEC_FUNCTION(f)) {
throw MakeTypeError("called_non_callable", [f]);
}
var needs_wrapper = false;
if (IS_NULL_OR_UNDEFINED(receiver)) {
receiver = %GetDefaultReceiver(f) || receiver;
} else {
needs_wrapper = SHOULD_CREATE_WRAPPER(f, receiver);
}
var stepping = DEBUG_IS_ACTIVE && %DebugCallbackSupportsStepping(f);
for (var i = 0; i < length; i++) {
if (i in array) {
var element = array[i];
// Prepare break slots for debugger step in.
if (stepping) %DebugPrepareStepInIfStepping(f);
var new_receiver = needs_wrapper ? ToObject(receiver) : receiver;
if (%_CallFunction(new_receiver, element, i, array, f)) return true;
}
}
return false;
}

去掉里面的各种参数的类型检查,简化为以下代码后

function jsSome(array, receiver) {
let length = array.length;
for (let i = 0; i < length; i++) {
let element = array[i];
if (receiver(element, i, array)) return true;
}
return false;
}

跑出来的成绩就和 lodash 几乎一致了。

这些类型检查我看了下,大部分对于底层泛用性来说是绝对必要的,但是对于确定的场景很多是不必要的。 所以对于 确定的场景 性能方面也不能盲目确信原生最佳。最针对的代码性能最优泛用性也最低。

上面附言的结论 100% 成立的前提是,js RumTime 下的很多实现应该还是 js ,例如 Array 的 some 函数,只是标记为了 [native code]

参考