Почему оптимизация компоновщика настолько бедна?

Недавно коллега указал мне, что компиляция всего в один файл создала гораздо более эффективный код, чем компиляция отдельных объектных файлов - , даже при оптимизации времени ссылки. Кроме того, общее время компиляции проекта значительно сократилось. Учитывая, что одной из основных причин использования С++ является эффективность кода, это меня удивило.

Ясно, что когда архиватор/компоновщик создает библиотеку из объектных файлов или связывает их с исполняемым файлом, даже штрафы за оптимизацию штрафуются. В приведенном ниже примере трилальная инкрустация стоит 1,8% в производительности, когда выполняется компоновщиком вместо компилятора. Похоже, что технология компилятора должна быть достаточно продвинута, чтобы обрабатывать довольно распространенные ситуации, подобные этому, но этого не происходит.

изменить:

Вот простой пример использования Visual Studio 2008:

#include <cstdlib>
#include <iostream>
#include <boost/timer.hpp>

using namespace std;

int foo(int x);
int foo2(int x) { return x++; }

int main(int argc, char** argv)
{
  boost::timer t;

  t.restart();
  for (int i=0; i<atoi(argv[1]); i++)
    foo (i);
  cout << "time : " << t.elapsed() << endl;

  t.restart();
  for (int i=0; i<atoi(argv[1]); i++)
    foo2 (i);
  cout << "time : " << t.elapsed() << endl;
}

foo.cpp

int foo (int x) { return x++; }

результаты выполнения: производительность 1,8% связана с использованием связанного foo вместо встроенного foo2.

$ ./release/testlink.exe  100000000
time : 13.375
time : 13.14

И да, флаги оптимизации компоновщика (/LTCG) включены.

Ответ 1

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

Кстати, я сожалею, что отвлек ваш первоначальный вопрос на обсуждение ltcg, теперь я понимаю, что ваш вопрос был немного другим, больше касался времени ссылки и времени компиляции. Статические оптимизации возможны/доступны.

Ответ 2

Ваш коллега устарел. Эта технология существует с 2003 года (в компиляторе MS С++): /LTCG. Генерирование временного кода канала имеет дело именно с этой проблемой. Из того, что я знаю, GCC имеет эту функцию на радаре для компилятора следующего поколения.

LTCG не только оптимизирует код, как встроенные функции для модулей, но и фактически переводит код для оптимизации местоположения и ветвления кэша для конкретной нагрузки, см. Profile-Guided Оптимизации. Эти параметры обычно зарезервированы только для версий Release, поскольку сборка может занять несколько часов: она свяжет инструментальный исполняемый файл, запускает профилирующую нагрузку и затем снова связывается с результатами профилирования. Ссылка содержит сведения о том, что именно оптимизировано с помощью LTCG:

Вложение. Например, если существует функция А, которая часто вызывает функцию B, а функция B - относительно небольшой, затем ориентированный на профиль оптимизация будет встроенной функцией B в функции А.

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

Распределение регистров. Оптимизация с помощью результаты профиля распределение регистров.

Оптимизация базового блока - базовый блок оптимизация позволяет обычно выполнять базовые блоки, которые временно выполняются в пределах данного кадра, который должен быть помещен в тот же набор страниц (местность). Эта минимизирует количество используемых страниц, таким образом минимизируя накладные расходы памяти.

Оптимизация размера/скорости - Функции где программа тратит много времени могут быть оптимизированы для скорости.

Макет функций - на основе вызова график и профилированный вызывающий/вызываемый поведение, функции, которые, как правило, вдоль одного и того же пути выполнения помещенный в том же разделе.

Оптимизация условной ветки - с датчики стоимости, управляемые профилем оптимизация может найти, если данный значение в инструкции switch используется чаще других. Эта значение может быть выведено из switch. То же самое можно сделать с инструкциями if else, где оптимизатор может заказать if/else так что либо if, либо блок сначала помещается в зависимости от того, какой блок чаще повторяется.

Разделение кода мертвого кода - код, который не вызываемый во время профилирования перемещается к специальному разделу, который прилагается до конца набора разделов. Это эффективно удерживает этот раздел из часто используемых страниц.

Разделение кода EH - код EH, будучи исключительно выполненным, может часто перемещаются в отдельный раздел когда оптимизация на основе профиля может определить, что существуют исключения только в исключительных условиях.

Память Intrinsics. Расширение внутренности могут быть решены лучше, если это может быть определено, если внутренняя часто называется. Внутренняя также оптимизироваться на основе блока размер перемещений или копий.

Ответ 3

ваш коллега умнее большинства из нас. Даже если это кажется грубым подходом вначале, проект, вложенный в один .cpp файл, имеет одну вещь, к которой другие подходы, такие как оптимизация времени ссылки, не имеет и не будет в течение - надежности

Однако вы спросили об этом 2 года назад, и я свидетельствую, что с тех пор многое изменилось (по крайней мере, с g++), devirtualization намного надежнее, например