C++ 编译器会将这个表达式优化为一个临时常量,而不是每次迭代都解析它吗?
我有以下循环:C++ 编译器会将这个表达式优化为一个临时常量,而不是每次迭代都解析它吗?,c++,loops,optimization,expression,C++,Loops,Optimization,Expression,我有以下循环: for (unique_ptr<Surface>& child : Children) { child->Gather(hit); if (hit && HitTestMode == HitTestMode::Content && child->MouseOver && !mouseOver) { mouseOver = true; } } 转换为一
for (unique_ptr<Surface>& child : Children)
{
child->Gather(hit);
if (hit && HitTestMode == HitTestMode::Content && child->MouseOver && !mouseOver)
{
mouseOver = true;
}
}
转换为一个临时常量,并使用它,而不是在每次迭代中解析表达式,类似于我这样做:
bool contentMode = hit && HitTestMode == HitTestMode::Content;
for (unique_ptr<Surface>& child : Children)
{
child->Gather(hit);
if (contentMode && child->MouseOver && !mouseOver)
{
mouseOver = true;
}
}
bool contentMode=hit&&HitTestMode==HitTestMode::Content;
用于(唯一的儿童:儿童)
{
孩子->收集(命中);
if(contentMode&&child->MouseOver&&MouseOver)
{
mouseOver=true;
}
}
奖金问题:
正在检查
!mouseOver
值得(为了跳过条件mouseOver=true;
如果已经设置)?或者,不管怎样,简单地重新设置它会更快吗?这个优化是否会发生的答案取决于命中的对象是什么,命中的模式是什么,命中的模式是什么,命中的模式是什么?和命中的模式::内容是什么?
以及是否可能通过调用子对象->聚集()来改变它们
如果这些标识符是编译器可以证明没有修改的常量或局部变量,那么子表达式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;
}
您将注意到,hit
的检查已从循环中提升出来,如果它是false
,则运行除了调用child->Gather()
之外什么都不做的a循环
如果将hitmodetest
更改为一个作为函数参数传递的变量,因此它不再可能被调用子聚集(hit)
更改,然后,编译器还将提升对hittestmode
值的检查,使其脱离循环,跳转到只调用child->Gather()
的循环
使用本地hittestmode
时,使用-O2
将在循环之前计算hit&&hittestmode==hittestmode::Content
,并将结果存储在寄存器中,但它仍将在每个循环迭代中测试寄存器,而不是优化到一个单独的循环,而该循环甚至不需要进行测试
由于您特别询问了VS2013编译器(使用/Ox
和/Ot
选项),它似乎没有将任何检查(对于命中
或命中测试模式
)从循环中移除-它似乎只需将这些变量的值保留在寄存器中 可能不会。查看生成的汇编代码(使用g++-O-fverbose asm-S
)。关于性能,基准!(我认为不管怎样,只需再次设置鼠标上方的会更快)回答奖金问题:再次设置会更快。分配完任务后,我会添加一个break语句,因为这样您就不必对其他孩子进行检查了。@programmerjake谢谢。不过,我不能打断,因为不管怎样,都需要对每个孩子执行一个操作(我从我的原始示例中漏掉了这个-我现在将更新它以包括这个)。下面是一个有趣的帖子,讨论为什么if语句会很昂贵:@d7samurai“奖金问题”到底是什么?你能详细说明一下吗?你应该每个问题问一个问题,你不能给任何回答的人任何奖金。请停下来。你可以问另一个(相关的)问题。此外,为了更好的实践,你不应该关心微观优化的这些事情,除非你会注意到任何真正的问题。就让它过去吧,让它变得更有成效。出于我们在这里的实际目的,您的可编译示例完全封装了实际实现的结构方式,因此您的分析是正确的。由于我知道一些编译器不知道的东西(即HitTestMode
没有被child->Gather()
触及),我可以随意折叠并将它们移出循环。但是,您的回答让我意识到,我可能希望child->Gather()
能够在迭代过程中[hackly]更改HitTestMode
,因此将表达式保留为“unheisted”。。但令人失望的是,VS没有在这里提升任何东西。
#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;
}
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