Компилятор Intel C ++ понимает, что выполняется оптимизация - PullRequest
5 голосов
/ 04 февраля 2011

У меня есть сегмент кода, который прост:

for( int i = 0; i < n; ++i)
{
  if( data[i] > c && data[i] < r )
  {
    --data[i];
  }
}

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

Когда данные [i] были временно сохранены следующим образом:

for( int i = 0; i < n; ++i)
{
  const int tmp = data[i];
  if( tmp > c && tmp < r )
  {
    --data[i];
  }
}

Стало намного медленнее.Я не утверждаю, что это должно быть быстрее, но я не могу понять, почему это должно быть намного медленнее, компилятор должен быть в состоянии выяснить, следует ли использовать tmp или нет.

Но что более важно, когда япереместил сегмент кода в отдельную функцию, он стал примерно в четыре раза медленнее.Я хотел понять, что происходит, поэтому я посмотрел в opt-report, и в обоих случаях цикл векторизован и, похоже, выполняет одну и ту же оптимизацию.

Поэтому мой вопрос заключается в том, что может иметь такое значение дляфункция, которая не вызывается миллион раз, но сама по себе занимает много времени?Что искать в opt-отчете?

Я мог бы избежать этого, просто сохранив это в уме, но почему меня это беспокоит.

ОБНОВЛЕНИЕ:

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

Ответы [ 3 ]

4 голосов
/ 04 февраля 2011

Вы, вероятно, голодали, и компилятору приходится загружать и хранить. Я уверен, что инструкции по сборке x86 могут использовать адреса памяти, т. Е. Компилятор может сохранять эти регистры свободными. Но, сделав его локальным, вы можете изменить поведение по отношению к. псевдонимы и компилятор могут не доказать, что более быстрая версия имеет одинаковую семантику, особенно если здесь присутствует некая форма нескольких потоков, позволяющая изменить код.

Функция была медленнее, когда в новом сегменте, вероятно, потому что вызовы функций не только могут разорвать конвейер, но и создать низкую производительность кэша команд (есть дополнительный код для параметра push / pop / etc).

Урок: пусть компилятор выполняет оптимизацию, он умнее вас. Я не имею в виду, что как оскорбление, это умнее меня тоже. Но на самом деле, особенно компилятор Intel, эти парни знают, что они делают, ориентируясь на собственную платформу.

Редактировать: Что еще более важно, вы должны понимать, что компиляторы нацелены на оптимизацию неоптимизированного кода. Они не нацелены на распознавание полуоптимизированного кода. В частности, компилятор будет иметь набор триггеров для каждой оптимизации, и если вам случится написать свой код таким образом, что они не попадут, вы можете избежать оптимизации, выполняемой , даже если код семантически идентичен.

И вам также необходимо учитывать стоимость реализации. Не каждая функция, идеальная для встраивания, может быть встроенной - просто потому, что встраивание этой логики слишком сложно для обработки компилятором. Я знаю, что VC ++ редко будет встраиваться в циклы, даже если выгода от встраивания выгодна. Возможно, вы видели это в компиляторе Intel - авторы компилятора просто решили, что не стоит тратить время на его реализацию.

Я сталкивался с этим при работе с циклами в VC ++ - компилятор будет создавать разные сборки для двух циклов в несколько разных форматах, даже если они оба достигли одного и того же результата. Конечно, их Стандартная библиотека использовала идеальный формат. Вы можете наблюдать ускорение, используя std::for_each и функциональный объект.

1 голос
/ 04 февраля 2011

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

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

Если вы проверите и увидите, что код не удален, вы можете сообщить об этом команде компилятора Intel. Похоже, у них может быть ошибка.

0 голосов
/ 05 февраля 2011

Я удивлен, что это

for( int i = 0; i < n; ++i)
{
  const int tmp = data[i]; //?? declaration inside a loop
  if( tmp > c && tmp < r )
  {
    --data[i];
  }
}

компилируется вообще. Возможно смущает компилятор. Попробуйте

for( int tmp, i = 0; i < n; ++i)
{
  tmp = data[i];
  if( tmp > c && tmp < r )
  {
    --data[i];
  }
}

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

int tmp; // well if you must have your temporary, I don't see why you want it,
         // it costs you 1 register although that should not matter much here.
for( size_t i = 0; i < n; ++i)
{
  tmp = data[i];
  if( tmp > c && tmp < r )
  {
    --data[i];
  }
}

Опубликуйте результаты.

...