【问题标题】:How do I make the optimizer to properly put variables into registers?如何使优化器正确地将变量放入寄存器?
【发布时间】:2012-08-16 05:54:25
【问题描述】:

我有一个简单的函数,它通过引用获取两个变量:

void foo(int*& it2,
         bit_reader<big_endian_tag>& reader2)
{
    for(/* ... */)
    {
        *it2++ = boo(reader2.next());
        // it2++ => 0x14001d890 add qword ptr [r12], 0x4
    }
}

这里的问题是,对于it2reader2,优化器在循环期间让计算机写入内存而不是寄存器。

但是,以下代码在循环期间将变量正确地放入寄存器,但在循环之前和之后以不必要的副本形式产生了额外的开销:

void foo2(int*& it2,
         bit_reader<big_endian_tag>& reader2)
{
    auto reader = reader2;
    auto it     = it2;

    for(/* ... */)
    {
        *it++ = boo(reader.next());
        // it++ => 0x14001d890 add r15, 0x4
    }

    reader2 = reader;
    it2 = it;
}

例如

如何使第一个示例生成与第二个示例相同的代码,但没有额外的副本?

【问题讨论】:

  • 您希望如何更改 it2reader2 而无需写入存储它们的内存?
  • 我希望编译器在循环运行时将it2reader2 保留在寄存器中,并且只在循环结束时将它们写回内存。
  • 理论上可以,如果它还可以证明boo(reader.next()) 永远不会受到更新值的影响。
  • 我实际上希望写入到 L1 缓存,在这种情况下(没有存储加载依赖)可能和寄存器一样快。

标签: c++ visual-studio optimization


【解决方案1】:

问题是编译器无法证明it2 在函数内没有变化。 (嗯,它可以,但这远远超出了普通 C++ 编译器的预期功能。)

它怎么知道boo(reader2.next()); 不会改变值?考虑:

int* i = 0;

struct foo
{
    int myInt;
    int blah() { i = &myInt; return 5; }
};

void bar(int*& ptr, const foo& f)
{
    *ptr = f.blah(); // changes value of ptr!
}

int otherInt;
i = &otherInt;

bar(i, foo());

这不会为otherInt 分配任何东西,而在您转换后它会:

void bar(int*& ptr, const foo& f)
{
    int* ptrCopy = ptr;
    *ptrCopy = f.blah(); // changes ptr, but not ptrCopy
}

所以除非编译器可以证明行为是相同的,否则它不能进行优化。

C99 使用 restrict 关键字解决了这个问题,但 C++ 没有等效项。不过,大多数 C++ 编译器中都存在扩展,例如 __restrict____restrict

要在标准 C++ 中执行此操作,您只需明确并自己制作副本

【讨论】:

  • 我尝试将它们作为指针传递并使用 __restrict,但这没有任何区别。我想明确地复制是最好的方法。
  • @ronag:嗯,我自己从来没有真正使用过它,所以我帮不上什么忙。 :( 如果您的代码可以简化为一个独立且可编译的示例,我建议您问第二个问题。
【解决方案2】:

嗯,你不能。

当您通过非常量引用传递参数时,您要求编译器更新原始变量。所以它必须将新值写入内存。

【讨论】:

  • 我明白这一点,但它不必在每次循环迭代中写入内存,只需在循环结束时写入内存,就像我在第二个示例中所做的那样。
  • 编译器必须能够意识到这一点。表达式*it2++ = boo(reader2.next()); 对编译器来说真的很难分析,所以大多数人都不关心缓存可能从外部可见的值。
【解决方案3】:

这一切都是为了优化“内存层次结构”,直接在寄存器上进行计算是最快的,这就是为什么你真的很想从内存中取出东西并将其复制到寄存器中在计算之前上面的任何内容,然后将结果复制回您需要的内存位置。通过直接在寄存器上计算获得的性能通常会抵消在寄存器中加载和保存内存的成本。

如何确保将内存中的内容放入寄存器?例如

size_t size;
double* arr;
for (int i = 0; i < size - 1; ++i) {
    double a = arr[i];     // copy to register
    double b = arr[i + 1]; // copy to register
    b = a*b + b;           // make sure flop computation is done in registers
    arr[i] = b;            // copy back to memory
}

【讨论】:

    猜你喜欢
    • 2019-07-10
    • 2019-08-14
    • 2010-11-18
    • 1970-01-01
    • 1970-01-01
    • 2011-06-16
    • 1970-01-01
    • 2013-11-06
    • 1970-01-01
    相关资源
    最近更新 更多