Я столкнулся с непоследовательным поведением оптимизации с разными компиляторами для следующего кода:
class tester
{
public:
tester(int* arr_, int sz_)
: arr(arr_), sz(sz_)
{}
int doadd()
{
sm = 0;
for (int n = 0; n < 1000; ++n)
{
for (int i = 0; i < sz; ++i)
{
sm += arr[i];
}
}
return sm;
}
protected:
int* arr;
int sz;
int sm;
};
Функция doadd
имитирует некоторый интенсивный доступ к членам (игнорируйте переполнение в дополнение к этому вопросу). По сравнению с аналогичным кодом, реализованным как функция:
int arradd(int* arr, int sz)
{
int sm = 0;
for (int n = 0; n < 1000; ++n)
{
for (int i = 0; i < sz; ++i)
{
sm += arr[i];
}
}
return sm;
}
Метод doadd
работает в 1,5 раза медленнее, чем функция arradd
при компиляции в режиме Release с Visual С++ 2008. Когда я изменяю метод doadd
следующим образом (aliasing все участники с местными жителями):
int doadd()
{
int mysm = 0;
int* myarr = arr;
int mysz = sz;
for (int n = 0; n < 1000; ++n)
{
for (int i = 0; i < mysz; ++i)
{
mysm += myarr[i];
}
}
sm = mysm;
return sm;
}
Временные ряды становятся примерно одинаковыми. Правильно ли я заключил, что это недостающая оптимизация компилятором Visual С++? g++
, похоже, делает это лучше и запускает как функцию-член, так и нормальную функцию с той же скоростью при компиляции с помощью -O2
или -O3
.
Бенчмаркинг выполняется путем вызова функции doadd
и arradd
на некотором достаточно большом массиве (несколько миллионов целых чисел).
EDIT: Некоторые мелкозернистые тесты показывают, что основным виновником является член sm
. Замена всех остальных локальными версиями по-прежнему делает рабочую среду длинной, но после замены sm
на mysm
время выполнения становится равным версии функции.
Разрешение
Не согласен с ответами (извините, ребята), я избавился от своей лени и нырнул в списки разборки для этого кода. Мой ответ ниже обобщает результаты. Короче: он не имеет ничего общего с псевдонимом, он имеет отношение к разворачиванию цикла и с некоторыми странными эвристиками MSVC применяется при выборе того, какой цикл нужно развернуть.