C/С++ оптимизирует прочь проверки, чтобы проверить, была ли уже запущена функция до - программирование
Подтвердить что ты не робот

C/С++ оптимизирует прочь проверки, чтобы проверить, была ли уже запущена функция до

Скажем, у вас есть функция в C/С++, которая ведет себя определенным образом при первом запуске. И тогда все остальные времена ведут себя по-другому (см. Ниже, например). После запуска в первый раз оператор if становится избыточным и может быть оптимизирован, если скорость важна. Есть ли способ сделать эту оптимизацию?

bool val = true; 

void function1() {

   if (val == true) {
      // do something
      val = false; 
   }
   else {
      // do other stuff, val is never set to true again 
   }

}
4b9b3361

Ответ 1

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

Тем не менее, вы можете использовать обратные вызовы:

#include <iostream>
using namespace std;
typedef void (*FunPtr) (void);
FunPtr method;
void subsequentRun()
{
    std::cout << "subsequent call" << std::endl;
}
void firstRun()
{
    std::cout << "first run" << std::endl;
    method = subsequentRun;  
}
int main()
{
    method = firstRun;
    method();
    method();
    method();
}

выводит результат:

первый запуск
последующий вызов
последующий вызов

Ответ 2

gcc имеет встроенную функцию, которая позволяет сообщать о реализации предсказания ветвления:

 __builtin_expect 

http://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html

Например, в вашем случае:

bool val = true; 

void function1()
{
    if (__builtin_expect(val, 0)) {
       // do something
       val = false; 
    }
    else {
      // do other stuff, val is never set to true again 
    }
}

Ответ 3

Вы можете использовать указатель на функцию, но в любом случае потребуется косвенный вызов:

void (*yourFunction)(void) = &firstCall;

void firstCall() {
 ..
 yourFunction = &otherCalls;
}

void otherCalls() {
 ..
}

void main()
{
  yourFunction();
}

Ответ 4

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

Ответ 5

Вместо глобальной переменной вы можете использовать переменную-член static.

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

Ответ 6

Компилятор может оптимизировать только то, что известно во время компиляции.

В вашем случае значение val известно только во время выполнения, поэтому его невозможно оптимизировать.

Тест if очень быстрый, вам не стоит беспокоиться о его оптимизации.

Ответ 7

Компиляторы, такие как g++ (и я уверен, что msvc) поддерживают создание данных профиля при первом запуске, а затем используют эти данные, чтобы лучше угадать, какие ветки наиболее вероятны, и соответственно оптимизировать их. Если вы используете gcc, посмотрите на параметр -fprofile-generate.

Ожидаемое поведение заключается в том, что компилятор будет оптимизировать этот оператор if таким образом, что else будет упорядочен первым, что позволит избежать операции jmp во всех ваших последующих вызовах, делая ее довольно быстро, как если бы она не была там, особенно если вы вернетесь где-нибудь в этом другом (таким образом, избегая необходимости перескакивать через инструкции "if" )

Ответ 8

Один из способов сделать эту оптимизацию - разделить функцию на две части. Вместо:

void function1()
{
    if (val == true) {
        // do something
        val = false; 
    } else {
       // do other stuff
    }
}

Сделайте это:

void function1()
{
    // do something
}

void function2()
{
   // do other stuff
}

Ответ 9

Одна вещь, которую вы можете сделать, это поместить логику в конструктор объекта, который затем определяется static. Если такой объект static возникает в области блока, конструктор запускается в первый раз, когда выполняется выполнение этой области. Проверка только один раз испускается компилятором.

Вы также можете поместить объекты static в область видимости файла, а затем они инициализируются до вызова main.

Я даю этот ответ, потому что, возможно, вы не используете эффективные классы С++.

(Что касается C/C++, такого языка нет. Существует C и есть С++. Вы работаете в C, который также должен компилироваться как С++ (иногда называемый, неофициально, "Clean C" ), или вы действительно работает на С++?)


Что такое "Чистый C" и как он отличается от стандарта C?

Ответ 10

Чтобы оставаться компилятором INDEPENDENT, вы можете закодировать части if() в одной функции и else{} в другой. почти все компиляторы оптимизируют if() else{} - поэтому, как только LIKELY будет else{} - следовательно, код иногда исполняемый код в if(), а остальные в отдельной функции, которая вызвала в else

Ответ 11

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

void function() {
    static bool firstRun = true;
    if (firstRun) {
        firstRun = false;
        ...
    }
    else {
        ...
    }
}

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

Это может быть хорошо использовано с решением @ouah.