我有以下循环:

for (unique_ptr<Surface>& child : Children)
{
    child->Gather(hit);

    if (hit && HitTestMode == HitTestMode::Content && child->MouseOver && !mouseOver)
    {
        mouseOver = true;
    }
}


我想知道编译器(我使用Visual Studio 2013,在Win7上面向x64)是否会优化表达式

hit && HitTestMode == HitTestMode::Content


到一个临时常量中,而不是在每次迭代时都解析表达式,类似于我做这样的事情:

bool contentMode = hit && HitTestMode == HitTestMode::Content;

for (unique_ptr<Surface>& child : Children)
{
    child->Gather(hit);

    if (contentMode && child->MouseOver && !mouseOver)
    {
        mouseOver = true;
    }
}


奖励问题:

是否值得检查!mouseOver(如果已经设置了条件mouseOver = true;,则跳过该条件)?还是简单地再次设置它是否更快?

最佳答案

是否可以进行优化的答案取决于hitHitTestModeHitTestMode::Content是什么,以及是否有可能通过调用child->Gather()来更改它们。

如果那些标识符是编译器可以证明未修改的常量或局部变量,则很有可能将悬挂子表达式hit && HitTestMode == HitTestMode::Content

例如,考虑以下示例的可编译版本:

#include <memory>
#include <vector>

using namespace std;

class Surface
{
public:
    void Gather(bool hit);
    bool MouseOver;
};

enum class HitTestMode
{
    Content = 1,
    Foo = 3,
    Bar = 4,
};


extern HitTestMode hittestmode;

bool anyMiceOver( vector<unique_ptr<Surface> > & Children, bool hit)
{
    bool mouseOver = false;

    for (unique_ptr<Surface>& child : Children)
    {
        child->Gather(hit);

        if (hit && hittestmode == HitTestMode::Content && child->MouseOver && !mouseOver)
        {
            mouseOver = true;
        }
    }

    return mouseOver;
}


当使用带有-O3优化选项的g ++ 4.8.1(mingw)进行编译时,您将获得以下循环代码段(添加了注释):

    mov rbx, QWORD PTR [rcx]      ; Children.begin()
    mov rsi, QWORD PTR 8[rcx]     ; Children.end()
    cmp rbx, rsi
    je  .L8                       ; early exit if Children is empty

    test    dl, dl                ; hit == 0?
    movzx   edi, dl
    je  .L5                       ; then goto loop L5

    xor ebp, ebp
    mov r12d, 1
    jmp .L7
    .p2align 4,,10
.L6:
    add rbx, 8
    cmp rsi, rbx                  ; check for end of Children
    je  .L2
.L7:
    mov rcx, QWORD PTR [rbx]
    mov edx, edi
    call    _ZN7Surface6GatherEb  ; call child->Gather(hit)
    cmp DWORD PTR hittestmode[rip], 1  ; check hittestmode
    jne .L6
    mov rax, QWORD PTR [rbx]      ; check child->MouseOver
    cmp BYTE PTR [rax], 0
    cmovne  ebp, r12d             ; set mouseOver appropriately
    jmp .L6
    .p2align 4,,10

.L5:                             ; loop L5 is run only when hit == 0
    mov rcx, QWORD PTR [rbx]     ; get net element in Children
    mov edx, edi
    add rbx, 8
    call    _ZN7Surface6GatherEb ; call child->Gather(hit)
    cmp rsi, rbx
    jne .L5

.L8:
    xor ebp, ebp
.L2:
    mov eax, ebp
    add rsp, 32
    pop rbx
    pop rsi
    pop rdi
    pop rbp
    pop r12
    ret


您会注意到hit的检查已从循环中吊起-如果它是false,则运行只调用child->Gather()的循环。

如果hitmodetest更改为作为函数参数传递的变量,因此不再可能通过调用child-Gather(hit)进行更改,则编译器还将取消对hittestmode值的检查。循环并跳转到只调用child->Gather()的循环。

对于本地hittestmode,使用-O2会在循环之前计算hit && hittestmode == HitTestMode::Content并存储到寄存器中,但仍会在每次循环迭代中测试寄存器,而不是优化为一个甚至不会循环的单独循环麻烦测试。

由于您是专门询问VS2013编译器(使用/Ox/Ot选项)的,因此它似乎并没有提升或优化循环中的任何检查(对于hithittestmode)-似乎要做的是将这些变量的值保留在寄存器中。

10-01 16:06