cas*_*der 8 c++ optimization gcc coding-style
在查看有关优化的一些问题时,对于最有效地使用优化器的编码实践问题,这个接受的答案激起了我的好奇心.断言是局部变量应该用于函数中的计算,而不是输出参数.有人建议这将允许编译器进行额外的优化,否则不可能.
因此,为示例Foo类编写一段简单的代码并使用g ++ v4.4和-O2编译代码片段会产生一些汇编器输出(使用-S).汇编程序列表的部分只包含如下所示的循环部分.在检查输出时,两个循环似乎几乎相同,只有一个地址不同.该地址是第一个示例的输出参数或第二个示例的局部变量的指针.
无论是否使用局部变量,实际效果似乎都没有变化.所以问题分为3部分:
a)即使给出提示,GCC也没有进行额外的优化;
b)GCC 在两种情况下都成功优化,但不应该;
c)GCC是否在两种情况下都成功优化,并且正在生成C++标准定义的兼容输出?
这是未经优化的功能:
void DoSomething(const Foo& foo1, const Foo* foo2, int numFoo, Foo& barOut)
{
for (int i=0; i<numFoo, i++)
{
barOut.munge(foo1, foo2[i]);
}
}
Run Code Online (Sandbox Code Playgroud)
和相应的组装:
.L3:
movl (%esi), %eax
addl $1, %ebx
addl $4, %esi
movl %eax, 8(%esp)
movl (%edi), %eax
movl %eax, 4(%esp)
movl 20(%ebp), %eax ; Note address is that of the output argument
movl %eax, (%esp)
call _ZN3Foo5mungeES_S_
cmpl %ebx, 16(%ebp)
jg .L3
Run Code Online (Sandbox Code Playgroud)
这是重写的功能:
void DoSomethingFaster(const Foo& foo1, const Foo* foo2, int numFoo, Foo& barOut)
{
Foo barTemp = barOut;
for (int i=0; i<numFoo, i++)
{
barTemp.munge(foo1, foo2[i]);
}
barOut = barTemp;
}
Run Code Online (Sandbox Code Playgroud)
这是使用局部变量的函数的编译器输出:
.L3:
movl (%esi), %eax ; Load foo2[i] pointer into EAX
addl $1, %ebx ; increment i
addl $4, %esi ; increment foo2[i] (32-bit system, 8 on 64-bit systems)
movl %eax, 8(%esp) ; PUSH foo2[i] onto stack (careful! from EAX, not ESI)
movl (%edi), %eax ; Load foo1 pointer into EAX
movl %eax, 4(%esp) ; PUSH foo1
leal -28(%ebp), %eax ; Load barTemp pointer into EAX
movl %eax, (%esp) ; PUSH the this pointer for barTemp
call _ZN3Foo5mungeES_S_ ; munge()!
cmpl %ebx, 16(%ebp) ; i < numFoo
jg .L3 ; recall incrementing i by one coming into the loop
; so test if greater
Run Code Online (Sandbox Code Playgroud)
Ray*_*hen 27
该答案中给出的示例并不是一个非常好的例子,因为对编译器无法解释的未知函数的调用.这是一个更好的例子:
void FillOneA(int *array, int length, int& startIndex)
{
for (int i = 0; i < length; i++) array[startIndex + i] = 1;
}
void FillOneB(int *array, int length, int& startIndex)
{
int localIndex = startIndex;
for (int i = 0; i < length; i++) array[localIndex + i] = 1;
}
Run Code Online (Sandbox Code Playgroud)
第一个版本优化得很差,因为它需要防止有人称之为的可能性
int array[10] = { 0 };
FillOneA(array, 5, array[1]);
Run Code Online (Sandbox Code Playgroud)
导致{1, 1, 0, 1, 1, 1, 0, 0, 0, 0 }自迭代以来i=1修改startIndex参数.
第二个不需要担心array[localIndex + i] = 1将修改的可能性,localIndex因为它localIndex是一个从未采用过地址的局部变量.
在汇编(Intel表示法,因为这是我使用的):
FillOneA:
mov edx, [esp+8]
xor eax, eax
test edx, edx
jle $b
push esi
mov esi, [esp+16]
push edi
mov edi, [esp+12]
$a: mov ecx, [esi]
add ecx, eax
inc eax
mov [edi+ecx*4], 1
cmp eax, edx
jl $a
pop edi
pop esi
$b: ret
FillOneB:
mov ecx, [esp+8]
mov eax, [esp+12]
mov edx, [eax]
test ecx, ecx
jle $a
mov eax, [esp+4]
push edi
lea edi, [eax+edx*4]
mov eax, 1
rep stosd
pop edi
$a: ret
Run Code Online (Sandbox Code Playgroud)
ADDED:这是一个例子,编译器的洞察力是Bar,而不是munge:
class Bar
{
public:
float getValue() const
{
return valueBase * boost;
}
private:
float valueBase;
float boost;
};
class Foo
{
public:
void munge(float adjustment);
};
void Adjust10A(Foo& foo, const Bar& bar)
{
for (int i = 0; i < 10; i++)
foo.munge(bar.getValue());
}
void Adjust10B(Foo& foo, const Bar& bar)
{
Bar localBar = bar;
for (int i = 0; i < 10; i++)
foo.munge(localBar.getValue());
}
Run Code Online (Sandbox Code Playgroud)
结果代码是
Adjust10A:
push ecx
push ebx
mov ebx, [esp+12] ;; foo
push esi
mov esi, [esp+20] ;; bar
push edi
mov edi, 10
$a: fld [esi+4] ;; bar.valueBase
push ecx
fmul [esi] ;; valueBase * boost
mov ecx, ebx
fstp [esp+16]
fld [esp+16]
fstp [esp]
call Foo::munge
dec edi
jne $a
pop edi
pop esi
pop ebx
pop ecx
ret 0
Adjust10B:
sub esp, 8
mov ecx, [esp+16] ;; bar
mov eax, [ecx] ;; bar.valueBase
mov [esp], eax ;; localBar.valueBase
fld [esp] ;; localBar.valueBase
mov eax, [ecx+4] ;; bar.boost
mov [esp+4], eax ;; localBar.boost
fmul [esp+4] ;; localBar.getValue()
push esi
push edi
mov edi, [esp+20] ;; foo
fstp [esp+24]
fld [esp+24] ;; cache localBar.getValue()
mov esi, 10 ;; loop counter
$a: push ecx
mov ecx, edi ;; foo
fstp [esp] ;; use cached value
call Foo::munge
fld [esp]
dec esi
jne $a ;; loop
pop edi
fstp ST(0)
pop esi
add esp, 8
ret 0
Run Code Online (Sandbox Code Playgroud)
注意内部循环Adjust10A必须重新计算值,因为它必须防止foo.munge更改的可能性bar.
也就是说,这种优化风格并不是一种扣篮.(例如,我们可能已经得到了通过手动缓存相同的效果bar.getValue()进入localValue).它往往是量化操作最有帮助的,因为这些可以paralellized.