【问题标题】:Insufficient register optimization of inlined class method内联类方法寄存器优化不足
【发布时间】:2017-01-23 10:02:49
【问题描述】:

为什么编译器不优化下面代码示例中来自Iterator::Incr 的内联代码的寄存器使用? (Visual Studio 2015,/O2 优化设置)

当我使用本文底部显示的 C++ 代码并引用迭代器时,Iterator::Incr 方法 内联Interpolator::InterpolateFast 内。然而,生成的汇编代码显示 memory 访问Iterator::_rem,尽管这个类成员变量可以放置在 register 内以提高效率。

$LL2@Interpolat:
  00020 41 01 19     add     DWORD PTR [r9], ebx   ; memory access to Iterator::_rem (slow)
  00023 4d 63 01     movsxd  r8, DWORD PTR [r9]
  ...
  0003e 75 e0        jne     SHORT $LL2@Interpolat

当我编译复制迭代器的快速版本时,汇编代码将Iterator::_rem 放在处理器寄存器中,并且只使用一个内存访问数组元素本身。

$LL2@Interpolat:
; 699  :        _rem += _incr;
  00011 45 03 c2     add     r8d, r10d    ; Iterator::_rem placed inside registers (fast)
...

当使用对 Iterator 类的引用时,编译器似乎会假定该类已被修改或访问 InterpolateFast(例如,由并发线程),因此避免了寄存器优化。

如何在不复制迭代器的情况下使用处理器寄存器优化内联方法?

typedef unsigned int BYTE;

class Iterator
{
public:
    Iterator() {}
    Iterator (const Iterator& it) :
        _rem (it._rem), _incr (it._incr) {}

    inline int Incr (const BYTE* &pSrc)
    {
        _rem += _incr;
        pSrc += _rem >> 16; 
        return _rem;
    }

private:
    int _rem;
    int _incr;
    friend class Interpolator;
};

class Interpolator
{
public:
    Interpolator (BYTE* p) : _p (p) {}
    int InterpolateFast (int len)
    {
        int sum = 0;
        const BYTE *p = _p;
        Iterator& it (_it);   // slow version, memory accesses to it._rem
        // Iterator it (_it); // fast version, registers optimized
        while (len--)
        {
            int rem = it.Incr (p);
            sum += p[0] * rem;
        }
        return sum;
    }

private:
    Iterator _it;
    const BYTE*    _p;
};

int main()
{
    BYTE arr[1000];
    Interpolator ip (arr);
    volatile int sum = ip.InterpolateFast (1000);
    return 0;
}

(请注意,本文的代码已被简化,没有有意义的功能。)

【问题讨论】:

  • 无法编译。你能发布一个 MCVE 吗?
  • 它看起来像一个指针别名问题 - 我不知道 M$ 编译器选项,但对我来说,但尝试 /Oa 或 /Ow 看看它是否有助于“慢”版本。如果是,那么可能是通过代码修改。
  • @RichardHodges:我更新了示例。
  • @Anty:MS VC++ 编译器 (VS2015) 似乎不支持编译器选项 /Oa /Ow(“忽略未知选项”)。我正在为没有 .NET 的 x64 非托管 C++ 进行编译。

标签: c++ optimization visual-studio-2015


【解决方案1】:

问题在于,在函数结束时,_it 必须已被对 Incr 的调用修改。有了副本,唯一需要更新的对象就被销毁了,所以更新在外部是不可见的。

显然编译器可以在循环结束时更新内存,但在出现提前返回或异常的情况下这很棘手。您和我都可以看到这不适用于这里,但是编译器可能已经丢失了。

还有一个问题是,如果循环内有更复杂的调用,编译器需要确保_it在循环内更新。

最简单的解决方法是:

    Iterator it (_it); // fast version, registers optimized
    while (len--)
    {
        int rem = it.Incr (p);
        sum += p[0] * rem;
    }
    _it = it;  // ****** Added line
    return sum;

顺便说一句,您是否有时间表明内存访问很慢?在第一次迭代之后,它将位于不比寄存器慢多少的 L1 缓存中。 (This answer 建议可能需要额外的 3-5 个周期,这可能会与其他内容重叠。)

【讨论】:

  • 复制构造函数的变通方法有帮助,但是在我更复杂的原始代码(我不能在这里发布)中,仍然有一个不必要的内存访问。我真的很想在不使用解决方法的情况下解决这个问题,因为我认为在这种情况下生成的代码会变得更好。 “慢”和“快”版本之间的性能差异约为 20%,并且会影响大部分代码库。
  • 好的,你有测量值。那挺好的。您是否考虑过使用英特尔编译器而不是 MSVC 编译器? (或者可能是 VS2017 编译器。)除此之外,要么调整 C++ 直到编译器生成所需的输出,要么编写一些内联汇编程序。
  • 很难将新的编译器集成到我们当前的构建过程中。使用 64 位代码时,内联汇编器不可用——您必须设置外部汇编器文件。这相当复杂,因为 InterpolateFast 方法在我的版本中是模板化的。我仍然没有放弃希望使用特殊的编译器标志、编译指示等来优化代码。
  • 英特尔编译器旨在替代 MS 编译器。如果这里有一个编译指示可以帮助你,我认为你是在做梦。不过祝你好运。
猜你喜欢
  • 2011-08-28
  • 2017-03-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多