为什么访问先前创建的变量比访问刚刚声明的变量需要更长的时间?

本文关键字:访问 变量 时间 创建 为什么 声明 | 更新日期: 2023-09-27 18:06:28

我最近运行了一个基准测试,看看在变量声明块的末尾或之后声明的变量是否访问时间更少。

基准代码(选定变量声明在块的末尾),

// 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:12262毫秒。

可以看到访问"new "变量在上面的基准测试是23.18% (2842.3 ms)更快,但为什么呢?

为什么访问先前创建的变量比访问刚刚声明的变量需要更长的时间?

通常,在世界上基本上任何优化编译器中,未使用的局部变量都会被优化删除。你只写了大部分的变量。这是一个删除物理存储的简单例子。

逻辑本地和物理存储之间的关系是非常复杂的。它们可能被删除、注册或泄漏。

因此,不要认为var _ = a1;实际上导致从a1读取和向_写入。

JIT在具有许多(我认为是64)局部变量的函数中关闭了一些优化,因为一些算法在局部变量数量上的运行时间是二次的。也许这就是为什么这些局部变量会影响性能。

尝试用更少的变量,你将无法区分这个函数的变体。

或者,用vc++、GCC或Clang试试。它们都应该删除整个循环。如果他们不去,我会很失望的。

我不认为你在这里衡量的是相关的东西。无论基准测试的结果如何-它对实际代码没有任何帮助。如果这是一个有趣的案例,我会看一下拆卸,但正如我说的,我认为这是不相关的。无论我发现什么,都不会是一个有趣的发现。

如果你想了解编译器通常生成什么代码,你可能应该编写一些简单的函数并查看生成的机器码。

试着在汇编/更接近硬件的地方思考,它可能是这样的:在更快的版本中,您仍然将先前访问的变量z2的地址存储在当前寄存器中,然后可以直接再次使用它,而无需更改其内容(=重新计算正确的内存地址)来进行写入和读取。

它可能是由解释器/编译器完成的自动优化。在循环结束时,你是否尝试过其他变量来代替z2进行W/R测试?如果你用x2或y2或者中间的其他变量会怎样?除z2之外的所有变量的访问时间是相等的还是不同的?