gpt4 book ai didi

javascript - 奇怪的 JavaScript 性能

转载 作者:可可西里 更新时间:2023-11-01 01:59:11 25 4
gpt4 key购买 nike

当我在 JavaScript 中实现 ChaCha20 时,我偶然发现了一些奇怪的行为。

我的第一个版本是这样构建的(我们称之为“封装版本”):

function quarterRound(x, a, b, c, d) {
x[a] += x[b]; x[d] = ((x[d] ^ x[a]) << 16) | ((x[d] ^ x[a]) >>> 16);
x[c] += x[d]; x[b] = ((x[b] ^ x[c]) << 12) | ((x[b] ^ x[c]) >>> 20);
x[a] += x[b]; x[d] = ((x[d] ^ x[a]) << 8) | ((x[d] ^ x[a]) >>> 24);
x[c] += x[d]; x[b] = ((x[b] ^ x[c]) << 7) | ((x[b] ^ x[c]) >>> 25);
}

function getBlock(buffer) {
var x = new Uint32Array(16);

for (var i = 16; i--;) x[i] = input[i];
for (var i = 20; i > 0; i -= 2) {
quarterRound(x, 0, 4, 8,12);
quarterRound(x, 1, 5, 9,13);
quarterRound(x, 2, 6,10,14);
quarterRound(x, 3, 7,11,15);
quarterRound(x, 0, 5,10,15);
quarterRound(x, 1, 6,11,12);
quarterRound(x, 2, 7, 8,13);
quarterRound(x, 3, 4, 9,14);
}
for (i = 16; i--;) x[i] += input[i];
for (i = 16; i--;) U32TO8_LE(buffer, 4 * i, x[i]);
input[12]++;
return buffer;
}

为了减少不必要的函数调用(参数开销等),我删除了 quarterRound 函数并将其内容内联(这是正确的;我根据一些测试向量对其进行了验证):

function getBlock(buffer) {
var x = new Uint32Array(16);

for (var i = 16; i--;) x[i] = input[i];
for (var i = 20; i > 0; i -= 2) {
x[ 0] += x[ 4]; x[12] = ((x[12] ^ x[ 0]) << 16) | ((x[12] ^ x[ 0]) >>> 16);
x[ 8] += x[12]; x[ 4] = ((x[ 4] ^ x[ 8]) << 12) | ((x[ 4] ^ x[ 8]) >>> 20);
x[ 0] += x[ 4]; x[12] = ((x[12] ^ x[ 0]) << 8) | ((x[12] ^ x[ 0]) >>> 24);
x[ 8] += x[12]; x[ 4] = ((x[ 4] ^ x[ 8]) << 7) | ((x[ 4] ^ x[ 8]) >>> 25);
x[ 1] += x[ 5]; x[13] = ((x[13] ^ x[ 1]) << 16) | ((x[13] ^ x[ 1]) >>> 16);
x[ 9] += x[13]; x[ 5] = ((x[ 5] ^ x[ 9]) << 12) | ((x[ 5] ^ x[ 9]) >>> 20);
x[ 1] += x[ 5]; x[13] = ((x[13] ^ x[ 1]) << 8) | ((x[13] ^ x[ 1]) >>> 24);
x[ 9] += x[13]; x[ 5] = ((x[ 5] ^ x[ 9]) << 7) | ((x[ 5] ^ x[ 9]) >>> 25);
x[ 2] += x[ 6]; x[14] = ((x[14] ^ x[ 2]) << 16) | ((x[14] ^ x[ 2]) >>> 16);
x[10] += x[14]; x[ 6] = ((x[ 6] ^ x[10]) << 12) | ((x[ 6] ^ x[10]) >>> 20);
x[ 2] += x[ 6]; x[14] = ((x[14] ^ x[ 2]) << 8) | ((x[14] ^ x[ 2]) >>> 24);
x[10] += x[14]; x[ 6] = ((x[ 6] ^ x[10]) << 7) | ((x[ 6] ^ x[10]) >>> 25);
x[ 3] += x[ 7]; x[15] = ((x[15] ^ x[ 3]) << 16) | ((x[15] ^ x[ 3]) >>> 16);
x[11] += x[15]; x[ 7] = ((x[ 7] ^ x[11]) << 12) | ((x[ 7] ^ x[11]) >>> 20);
x[ 3] += x[ 7]; x[15] = ((x[15] ^ x[ 3]) << 8) | ((x[15] ^ x[ 3]) >>> 24);
x[11] += x[15]; x[ 7] = ((x[ 7] ^ x[11]) << 7) | ((x[ 7] ^ x[11]) >>> 25);
x[ 0] += x[ 5]; x[15] = ((x[15] ^ x[ 0]) << 16) | ((x[15] ^ x[ 0]) >>> 16);
x[10] += x[15]; x[ 5] = ((x[ 5] ^ x[10]) << 12) | ((x[ 5] ^ x[10]) >>> 20);
x[ 0] += x[ 5]; x[15] = ((x[15] ^ x[ 0]) << 8) | ((x[15] ^ x[ 0]) >>> 24);
x[10] += x[15]; x[ 5] = ((x[ 5] ^ x[10]) << 7) | ((x[ 5] ^ x[10]) >>> 25);
x[ 1] += x[ 6]; x[12] = ((x[12] ^ x[ 1]) << 16) | ((x[12] ^ x[ 1]) >>> 16);
x[11] += x[12]; x[ 6] = ((x[ 6] ^ x[11]) << 12) | ((x[ 6] ^ x[11]) >>> 20);
x[ 1] += x[ 6]; x[12] = ((x[12] ^ x[ 1]) << 8) | ((x[12] ^ x[ 1]) >>> 24);
x[11] += x[12]; x[ 6] = ((x[ 6] ^ x[11]) << 7) | ((x[ 6] ^ x[11]) >>> 25);
x[ 2] += x[ 7]; x[13] = ((x[13] ^ x[ 2]) << 16) | ((x[13] ^ x[ 2]) >>> 16);
x[ 8] += x[13]; x[ 7] = ((x[ 7] ^ x[ 8]) << 12) | ((x[ 7] ^ x[ 8]) >>> 20);
x[ 2] += x[ 7]; x[13] = ((x[13] ^ x[ 2]) << 8) | ((x[13] ^ x[ 2]) >>> 24);
x[ 8] += x[13]; x[ 7] = ((x[ 7] ^ x[ 8]) << 7) | ((x[ 7] ^ x[ 8]) >>> 25);
x[ 3] += x[ 4]; x[14] = ((x[14] ^ x[ 3]) << 16) | ((x[14] ^ x[ 3]) >>> 16);
x[ 9] += x[14]; x[ 4] = ((x[ 4] ^ x[ 9]) << 12) | ((x[ 4] ^ x[ 9]) >>> 20);
x[ 3] += x[ 4]; x[14] = ((x[14] ^ x[ 3]) << 8) | ((x[14] ^ x[ 3]) >>> 24);
x[ 9] += x[14]; x[ 4] = ((x[ 4] ^ x[ 9]) << 7) | ((x[ 4] ^ x[ 9]) >>> 25);
}
for (i = 16; i--;) x[i] += input[i];
for (i = 16; i--;) U32TO8_LE(buffer, 4 * i, x[i]);
input[12]++;
return buffer;
}

但性能结果并不如预期:

Encapsulated performance

对比

Inline performance

虽然 Firefox 和 Safari 下的性能差异可以忽略不计或不重要,但 Chrome 下的性能下降是巨大的......知道为什么会这样吗?

P.S.:如果图片太小,请在新标签页中打开它们:)

PP.S.:这是链接:

Inlined

Encapsulated

最佳答案

发生回归是因为您在 V8 当前的优化编译器 Crankshaft 中的一个过程中遇到了错误。

如果您查看 Crankshaft 对缓慢的“内联”情况所做的操作,您会注意到 getBlock功能不断去优化。

为了看到你可以通过 --trace-deopt标记为 V8 并读取它转储到控制台的输出或使用名为 IRHydra 的工具.

我收集了内联和非内联情况下的 V8 输出,您可以在 IRHydra 中探索:

这是“内联”案例的显示内容:

method list

函数列表中的每个条目都是一次优化尝试。红色表示优化后的函数后来取消优化,因为违反了优化编译器所做的某些假设。

这意味着 getBlock不断优化和取消优化。在“封装”的情况下没有这样的事情:

enter image description here

在这里getBlock被优化一次,永远不会取消优化。

如果我们看里面getBlock我们将看到它是从 Uint32Array 加载的数组取消优化,因为此加载的结果是一个不适合 int32 的值值(value)。

enter image description here

这个 deopt 的原因有点令人费解。 JavaScript 唯一的数字类型是 double float 。用它进行所有计算会有些低效,因此优化 JIT 通常会尝试将整数值表示为优化代码中的实际整数。

Crankshaft 的最宽整数表示是 int32和一半 uint32值在其中无法表示。为了部分缓解此限制,Crankshaft 执行称为 uint32 分析 的优化过程。这个过程试图弄清楚表示 uint32 是否安全值作为 int32值 - 这是通过查看此 uint32 的方式来完成的使用值:一些操作,例如按位的,不关心“符号”,只关心单个位,可以教导其他操作(例如去优化或从整数到 double 的转换)以特殊方式处理 int32-that-is-actually-uint32。如果分析成功 - 所有使用 uint32 value are safe - 然后这个操作用特殊的方式标记,否则(一些使用被发现是不安全的)这个操作没有被标记并且如果它产生uint32就会被deopt。不适合 int32 的值范围(任何高于 0x7fffffff 的值)。

在这种情况下,分析没有标记 x[i]作为保险箱 uint32操作 - 因此当 x[i] 的结果时它正在取消优化在int32之外范围。不标注原因x[i]安全是因为它的用途之一,内联时由 inliner 创建的人工指令 U32TO8_LE , 被认为是不安全的。这是一个patch for V8这解决了问题,它还包含问题的一个小例子:

var u32 = new Uint32Array(1);
u32[0] = 0xFFFFFFFF; // this uint32 value doesn't fit in int32

function tr(x) {
return x|0;
// ^^^ - this use is uint32-safe
}
function ld() {
return tr(u32[0]);
// ^ ^^^^^^ uint32 op, will deopt if uses are not safe
// |
// \--- tr is inlined into ld and an hidden artificial
// HArgumentObject instruction was generated that
// captured values of all parameters at entry (x)
// This instruction was considered uint32-unsafe
// by oversight.
}

while (...) ld();

你没有在“封装”版本中遇到这个错误,因为 Crankshaft 自己的内衬在达到 U32TO8_LE 之前就用完了预算调用站点。正如您在 IRHydra 中看到的,只有前三个调用 quarterRound内联:

enter image description here

您可以通过更改 U32TO8_LE(buffer, 4 * i, x[i]) 来解决此错误至 U32TO8_LE(buffer, 4 * i, x[i]|0)仅使用 x[i]值 uint32-safe 并且不会更改结果。

关于javascript - 奇怪的 JavaScript 性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29424013/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com