Пример, приведенный в этом ответе, был не очень хорошим из-за вызова неизвестной функции, о которой компилятор не может много рассуждать.Вот лучший пример:
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;
}
Первая версия плохо оптимизируется, потому что она должна защищать от возможности того, что кто-то назвал ее как
int array[10] = { 0 };
FillOneA(array, 5, array[1]);
, что приведет к {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
ДОБАВЛЕНО: Вот пример, где понимание компилятором находится в 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());
}
Полученный код:
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
Обратите внимание, что внутренний цикл в Adjust10A
должен пересчитать значение, поскольку он должен защищать от возможности того, что foo.munge
изменилось bar
.
Тем не менее, этот стиль оптимизации - это не хлам.(Например, мы могли бы получить тот же эффект путем ручного кэширования bar.getValue()
в localValue
.) Это имеет тенденцию быть наиболее полезным для векторизованных операций, поскольку они могут быть парализованы.