为什么JavaScript处理结构数组比数组结构更快?

10

我一直在寻找一种高效的方法来处理JavaScript中的大型向量列表。我创建了一个性能测试套件,使用不同的数据结构执行原地标量-向量乘法:

AoS实现:

var vectors = [];
//
var vector;
for (var i = 0, li=vectors.length; i < li; ++i) {
    vector = vectors[i];
    vector.x = 2 * vector.x;
    vector.y = 2 * vector.y;
    vector.z = 2 * vector.z;
}

SoA实现:

var x = new Float32Array(N);
var y = new Float32Array(N);
var z = new Float32Array(N);
for (var i = 0, li=x.length; i < li; ++i) {
    x[i] = 2 * x[i];
    y[i] = 2 * y[i];
    z[i] = 2 * z[i];
}

实现AoS至少快了5倍。这让我很惊讶。AoS实现每次迭代比SoA实现多使用一个索引查找,并且引擎必须在没有保证数据类型的情况下工作。

为什么会发生这种情况?这是由于浏览器优化还是缓存未命中?

顺便说一句,当对向量列表执行加法时,SoA仍然略微更有效:

AoS:

var AoS1 = [];
var AoS2 = [];
var AoS3 = [];
//code for populating arrays
for (var i = 0; i < N; ++i) {
    AoS3[i].x = AoS1[i].x + AoS2[i].x;
}

SoA:

var x1 = new Float32Array(N);
var x2 = new Float32Array(N);
var x3 = new Float32Array(N);
for (var i = 0; i < N; ++i) {
    x3[i] = x1[i] + x2[i];
}

有没有一种方法可以告诉我,对于给定的数据结构,某个操作何时会更有效率/更低效率?

编辑:我未能强调SoA实现中使用了类型化数组,这就是为什么这种性能行为让我感到奇怪。尽管类型化数组提供了数据类型的保证,但普通的关联数组仍然更快。我还没有看到过这个问题的复制。

编辑2:当vector的声明被移动到准备代码中时,这种行为不再发生。当vectorfor循环旁边声明时,AoS表现更快。这对我来说没有多少意义,特别是因为引擎应该将其锚定到作用域的顶部。我不会进一步质疑这个问题,因为我怀疑测试框架存在问题。

编辑3:我从测试平台的开发人员那里得到了一个回复,他们确认性能差异是由于外部作用域查找。SoA仍然是最有效的,正如预期的那样。


请在此处发布您的代码,而不仅仅是链接到基准测试网站。 - Barmar
现代JavaScript引擎对对象访问进行了特殊的优化。 - Barmar
“Structure” 指的是对象,对吗? - OrangeDog
在AoS测试中,我的“结构”是关联列表。在SoA测试中,我没有实现“结构”,数组位于全局范围内。我认为这与性能无关。https://en.wikipedia.org/wiki/AOS_and_SOA - 16807
5
如果没有重复,与之相关的是:https://dev59.com/e2Qm5IYBdhLWcg3w2R5J。 - Heretic Monkey
@16807 顺便说一句,我发现 *= 2 更高效。不知道为什么。 - Veedrac
1个回答

3
用于基准测试的测试结构似乎有重叠,导致出现未定义或不希望的行为。更为整洁的测试(https://www.measurethat.net/Benchmarks/Show/474/0/soa-vs-aos)显示两者之间几乎没有差异,并且SOA的执行速度略快(30%)。
然而,当涉及到性能时,这些都不重要。这是微观优化的努力。你实际上是在比较有细微差别的O(n)和 O(n)。这种小百分比的差异总体上不会产生影响,因为O(n)被认为是可接受的时间复杂度。

而结构体数组每次迭代只需要1次查找。对关联数组进行属性查找(例如“vector.x”)仍然是一次查找。我计算了SoA的6次查找和AoS的7次查找。我不知道你从哪里得到了SoA的额外3次查找。复杂度是相同的,但考虑到有些应用程序中,5倍的改进可以带来天翻地覆的变化。 - 16807
属性查找和索引查找不同,因为集合的大小会影响查找所需的时间。您的对象上只有少量属性,而集合中可能有数千个项。 - Travis J
在创建一个没有交错代码的新测试时,我再也无法重现这种行为。 - 16807
7
“小幅度差异不会对整体产生影响,因为O(n)被认为是可接受的时间复杂度。”// 如果这是您代码的瓶颈,30%的改进并不是微不足道的。您不能仅仅因为知道O(n)是可接受的时间复杂度就这么说,而不了解上下文。 - Veedrac
2
你所提供的基准测试结果显示惊人的43%的性能提升!我认为你可能把数字搞混了: AOS比SOA慢30%。 SOA比AOS快43%。值得注意的是,在实践中,对于大型程序中的大型数组,收益可能会更大,因为SOA不会使用无关对象数据来破坏CPU缓存,同时使CPU预取器的工作更加轻松!实际上,根据上下文,这可能会将因素改变一个数量级(在极端情况下甚至可以理论上改变两个数量级!) - Martin Grönlund
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接