Почему этот код медленнее, даже если функция встроена? - PullRequest
2 голосов
/ 02 февраля 2010

У меня есть такой метод:

bool MyFunction(int& i)
{
  switch(m_step)
  {
    case 1:
       if (AComplexCondition)
       {
         i = m_i;
         return true;
       }

    case 2:
      // some code

    case 3:
      // some code
  }
}

Поскольку существует много операторов case (более 3) и функция становится большой, я попытался извлечь код в случае 1 и поместить его во встроенную функцию, например:

inline bool funct(int& i)
{
  if (AComplexCondition)
  {
    i = m_i;
    return true;
  }
  return false;
}
bool MyFunction(int& i)
{
  switch(m_step)
  {
    case 1:
       if (funct(i))
       {
         return true;
       }

    case 2:
      // some code

    case 3:
      // some code
  }
}

Кажется, этот код значительно медленнее, чем оригинал. Я проверил с -Winline, и функция встроена. Почему этот код медленнее? Я думал, что это будет эквивалентно. Единственное отличие, которое я вижу, - это еще одна условная проверка во второй версии, но я подумал, что компилятор сможет ее оптимизировать. Правильно?

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

Первая версия выглядит так:

mov
callq (Call to AComplexCondition())
test
je (doesn't jump)
mov (i = m_i)
movl (m_step = 1)

Вторая версия, которая немного медленнее, кажется проще.

movl (m_step = 1)
callq (Call to AComplexCondition())
test
je (doesn't jump)
mov (i = m_i)
xchg %ax,%ax (This is a nop I think)

Эти две версии, кажется, делают одно и то же, поэтому я до сих пор не знаю, почему вторая версия все еще медленнее.

Ответы [ 6 ]

3 голосов
/ 03 февраля 2010

Просто пройдитесь по нему. Установите точку останова, перейдите в окно разборки и начните шагать.

Все тайны исчезнут.

2 голосов
/ 02 февраля 2010

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

Что я предлагаю сделать:

Максимально изолируйте код и состояние, сохраняя при этом возможность замедления.

Затем зайдите в профиль. Имеет ли профилирование смысл? Теперь (если вы готовы к приключениям) разберите код и посмотрите, что g ++ делает по-другому. Сообщить об этих результатах здесь

1 голос
/ 03 февраля 2010

Это хороший вопрос. Дайте нам знать, что вы найдете. У меня есть несколько мыслей, в основном вытекающих из того, что компилятор больше не может разбить код, который вы ввели, но не гарантированный ответ.

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

  2. создание дополнительных случаев. Должна быть возможность вытащить некоторых из coditionals из заявления if и в некоторых обстоятельствах сделать дополнительную оценку ситуации. Это может устранить некоторые проверки.

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

1 голос
/ 03 февраля 2010

Ассемблер говорит вам что-нибудь о том, что происходит?Возможно, будет легче взглянуть на разборку, чем заставить нас догадываться, хотя я в целом согласен с идеей jmp iaimtomis.

1 голос
/ 03 февраля 2010

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

1 голос
/ 03 февраля 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...