UPDATE
现在V8有ReduceSetPrototypeHas
方法,因此它将在较新版本的 Node.js 或浏览器中进行优化。
https://github.com/v8/v8/commit/4c81827c8d6ca1d3d9b0cb6a2ef1264eb0f59524 https://github.com/v8/v8/commit/4c81827c8d6ca1d3d9b0cb6a2ef1264eb0f59524
TL;DR:V8 的 TurboFan 目前将 Map.has() 调用优化为 C++ 世界中的本机方法调用,但没有优化 Set.has() 调用。
不,为什么 Map.has 比 Set.has 快得多?
这也很有趣,因为它们必须使用相同的哈希表内部实现。
答案深入探讨如何TurboFan,V8的JIT编译器,做了优化。它利用节点海概念,你可以认为它是 AST
优化。 V8 通过用更快的表示替换节点海中的一些子树来进行优化(减少)。减少的最简单的例子是替换a = 1 + 2
to a = 3
.
其超强功能之一是它可以用对底层 C++ 实现的调用来替换一些 JS 方法调用,以消除开销。请参阅官方幻灯片,尤其是其中的几页这个链接 https://docs.google.com/presentation/d/1sOEF4MlF7LeO7uq-uThJSulJlTh--wgLeaVibsbb3tc/edit#slide=id.g5499b9c42_089查看其功能的示例。
然后,查看实际减少发生的代码。
In js-call-reducer.cc https://github.com/v8/v8/blob/65d9c441dfad3d06d5a2cf2857a71e2388e10d6f/src/compiler/js-call-reducer.cc#L7223 of V8, JSCall
of Map.has
将被本机函数调用替换:
Reduction JSCallReducer::ReduceMapPrototypeHas(Node* node) {
...(reading current nodes)...
Node* table = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSCollectionTable()), receiver,
effect, control);
Node* index = effect = graph()->NewNode(
simplified()->FindOrderedHashMapEntry(), table, key, effect, control);
Node* value = graph()->NewNode(simplified()->NumberEqual(), index,
jsgraph()->MinusOneConstant());
value = graph()->NewNode(simplified()->BooleanNot(), value);
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
(FindOrderedHashMapEntry
与FindOrderedHashTableEntry
实际上查找哈希表的系列方法。查看源代码来自here http://%20https://github.com/v8/v8/blob/65d9c441dfad3d06d5a2cf2857a71e2388e10d6f/src/builtins/builtins-collections-gen.cc#L2403.)
But 没有ReduceSetPrototypeHas
method,所以没有进行这样的优化Set.has
。这就是为什么Set.has()
慢于Map.has()
.
我确认本地构建的 V8 替换为以下代码可以使Set.has
and Map.has
基准测试结果几乎相同。
Reduction JSCallReducer::ReduceMapPrototypeHas(Node* node) {
return NoChange();
}
我不确定是否Set.has
是否应该优化,因为 V8 应该优化现实世界的应用,不适用于微基准测试结果。 (我也不知道添加新的减少量的缺点是什么以及有多大。)
(我不知道为什么升级 Node 6.2.0 -> 9.4.0Set.has()
慢 3 倍。)
See https://v8.dev/docs/turbofan https://v8.dev/docs/turbofan了解 TurboFan 内部结构的更多资源。