Подтвердить что ты не робот

Неиспользованные функциональные изменения

При попытке оценить разницу в производительности между push_back и std::inserter я столкнулся с очень странной проблемой производительности.

Рассмотрим следующий код:

#include <vector>
using container = std::vector<int>;
const int size  = 1000000;
const int count = 1000;

#ifdef MYOWNFLAG
void foo(std::insert_iterator<container> ist)
{
    for(int i=0; i<size; ++i)
        *ist++ = i;
}
#endif

void bar(container& cnt)
{
    for(int i=0; i<size; ++i)
        cnt.push_back(i);
}
int main()
{
    container cnt;
    for (int i=0; i<count; ++i)
    {
        cnt.clear();
        bar(cnt);
    }
    return 0;
}

В этом случае не определено, определено или нет MYOWNFLAG, функция foo не вызывается. Однако значение этого флага влияет на производительность:

$ g++ -g -pipe -march=native -pedantic -std=c++11 -W -Wall -Wextra -Werror -O3 -o bin/inserter src/inserter.cc && time ./bin/inserter
./bin/inserter  4,73s user 0,00s system 100% cpu 4,728 total

$ g++ -g -pipe -march=native -pedantic -std=c++11 -W -Wall -Wextra -Werror -O3 -o bin/inserter src/inserter.cc -DMYOWNFLAG && time ./bin/inserter
./bin/inserter  2,09s user 0,00s system 99% cpu 2,094 total

Обратите внимание, что если я изменяю прототип foo для использования std::back_insert_iterator, я получаю аналогичную производительность, как если бы я не устанавливал флаг.

Что происходит с оптимизацией компилятора???

ИЗМЕНИТЬ

Я использую gcc 4.9.2 20150304 (preerelease)

Repoduced

  • воспроизведенный stefan на ideone
  • воспроизведенный мной на другой машине с gcc 4.9.2
  • не воспроизводится мной на другой машине с gcc 4.6.3 и флаг -std = С++ 0x
4b9b3361

Ответ 1

Сначала я покажу вам волшебный трюк, как добиться этого без функции мусора. Затем я покажу вам, почему работает функция мусора. Итак, трюк:

Оригинал неэффективен (обратите внимание на мою машину примерно в два раза быстрее):

g++ -g -pipe -march=native -pedantic -std=c++11 -W -Wall -Wextra -Werror -O3 -o bin/inserter src/inserter.cc --param inline-unit-growth=200 && time ./bin/inserter
real    0m2.197s
user    0m2.200s
sys     0m0.000s

Теперь идет трюк (ваше определение все еще неактивно):

g++ -g -pipe -march=native -pedantic -std=c++11 -W -Wall -Wextra -Werror -O3 -o bin/inserter src/inserter.cc --param inline-min-speedup=2 && time ./bin/inserter
real    0m1.114s
user    0m1.100s
sys 0m0.010s

Примечание: разница в аргументе странного вида --param inline-min-speedup=2

Теперь я кратко опишу расследование:

  • В чем разница между быстрым и медленным? В медленной версии у нас есть неэффективный вызов emplace_back_aux внутри bar(), который магически встроен, когда ваш foo раскоментирован. Таким образом, мы можем заключить, что бар очень горячий, и инкрустация здесь сложная. И, скорее всего, вся эта ошибка связана с вложением.

  • Теперь с опцией -fdump-ipa-inline-details можно посмотреть на вставку дампов. Вы увидите разные соображения времени и размера. Трудно читать, и я не хочу вставлять сюда все подробности. Но общий результат изучения этой информации: GCC считает, что рост в размере модуля (в процентах) не стоит оценивать ускорение.

  • Что делать? Две возможности:

    3,1. Увеличьте размер модуля и общие оценки ускорения с помощью неиспользуемого кода foo, который использует правильные типы, такие как insert_iterator, чтобы вызвать emplace_back и коэффициент перемещения, чтобы быть больше, и достигнуть предела вложения (обратите внимание, что этот способ очень нестабилен - все может взорваться в других версии компилятора с улучшенными алгоритмами встраивания, и вам также должно быть действительно повезло, чтобы угадать, какой код работает).

    3,2. Или перейдите в листинг. Что я сказал GCC с предоставленным параметром, "рассмотрим возможность вложения даже больших функций с меньшим ускорением".

То есть. Есть много других параметров внутри GCC и других трюков, которые вы можете с ними сделать.