我最近运行了一个基准测试,以查看在变量声明块结束时或之后声明的变量的访问时间是否更少。
基准代码(在块结束时声明的选定变量),
// Benchmark 1
for (long i = 0; i < 6000000000; i++)
{
var a1 = 0;
var b1 = 0;
var c1 = 0;
// 53 variables later...
var x2 = 0;
var y2 = 0;
var z2 = 0;
z2 = 1; // Write
var _ = z2; // Read
}
基准代码(在块开始时声明的选定变量),
// Benchmark 2
for (long i = 0; i < 6000000000; i++)
{
var a1 = 0;
var b1 = 0;
var c1 = 0;
// 53 variables later...
var x2 = 0;
var y2 = 0;
var z2 = 0;
a1 = 1; // Write
var _ = a1; // Read
}
令我惊讶的是,结果(平均超过3次运行,不包括首次构建和没有优化)如下,
基准1:9,419.7毫秒。
基准2:12,262毫秒。
正如您所看到的那样,访问&#34;更新&#34;上述基准中的变量23.18%
(2842.3 ms
)更快,但为什么?
答案 0 :(得分:5)
通常,在世界上基本上任何优化编译器中都会通过优化删除未使用的本地。您只写大多数变量。这是删除其物理存储空间的简单案例。
逻辑本地与其物理存储之间的关系非常复杂。它们可能会被删除,注册或泄露。
所以不要认为var _ = a1;
实际上会导致a1
的读取和_
的写入。它什么都不做。
JIT在许多(我相信64个)局部变量的函数中关闭了一些优化,因为一些算法在本地数量上具有二次运行时间。也许这就是那些当地人影响表现的原因。
尝试使用较少的变量,您将无法区分此功能的变体。
或者,尝试使用VC ++,GCC或Clang。他们都应该删除整个循环。如果他们没有,我会非常失望。
我不认为你在这里测量相关的东西。无论你的基准测试结果如何 - 它都可以帮助你解决现实代码问题。如果这是一个有趣的案例,我会看看反汇编,但正如我所说,我认为这是无关紧要的。无论我发现什么,这都不是一个有趣的发现。
如果您想了解编译器通常生成的代码,您应该编写一些简单的函数并查看生成的机器代码。这可能非常具有教学意义。
答案 1 :(得分:1)
尝试在汇编程序中思考/更接近硬件,它可能是这样的: 在更快的版本中,您仍然具有存储在当前寄存器中的先前访问的变量z2的地址,然后可以直接再次使用该地址而无需更改其内容(=重新计算正确的存储器地址)以进行写入和读取。
它可以是由解释器/编译器完成的自动优化。 在循环结束时,您是否尝试过其他变量而不是z2进行W / R测试? 如果您使用x2或y2甚至中间的任何其他变量会发生什么? 除z2之外的所有变量的访问时间是否相等,或者它们是否也不同?