Встроенная скорость и оптимизация компилятора - PullRequest
3 голосов
/ 30 июля 2010

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

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

Мой вопрос: что вы думаете о том, почему это происходит? Это современная оптимизация компилятора? Будет ли это отсутствие больших вычислений в функции?

Любое понимание этого вопроса будет оценено. Заранее спасибо друзья.

#include < iostream >
#include < time.h >

// RESEARCH                                                   Jared Thomson 2010
////////////////////////////////////////////////////////////////////////////////
// Two functions that preform an identacle arbitrary floating point calculation
// one function is inline, the other is not.

double test(double a, double b, double c);
double inlineTest(double a, double b, double c);

double test(double a, double b, double c){
    a = (3.1415 / 1.2345) / 4 + 5;
    b = 9.999 / a + (a * a);
    c = a *=b;
    return c;
}

inline
double inlineTest(double a, double b, double c){
    a = (3.1415 / 1.2345) / 4 + 5;
    b = 9.999 / a + (a * a);
    c = a *=b;
    return c;
}

// ENTRY POINT                                                Jared Thomson 2010
////////////////////////////////////////////////////////////////////////////////
int main(){
    const unsigned int maxUINT = -1;
    clock_t start = clock();

    //============================ NON-INLINE TEST ===============================//
    for(unsigned int i = 0; i < maxUINT; ++i)
        test(1.1,2.2,3.3);

    clock_t end = clock();
    std::cout << maxUINT << " calls to non inline function took " 
              << (end - start)/CLOCKS_PER_SEC << " seconds.\n";

    start = clock();

    //============================ INLINE TEST ===================================//
    for(unsigned int i = 0; i < maxUINT; ++i)
        test(1.1,2.2,3.3);

    end = clock();
    std::cout << maxUINT << " calls to inline function took " 
              << (end - start)/CLOCKS_PER_SEC << " seconds.\n";

    getchar(); // Wait for input.
    return 0;
} // Main.

Сборочный выход

Pastebin

Ответы [ 9 ]

15 голосов
/ 30 июля 2010

Ключевое слово inline в основном бесполезно. Это только предложение. Компилятор может игнорировать его и отказываться встроить такую ​​функцию, а также может включать функцию, объявленную без ключевого слова inline.

Если вы действительно заинтересованы в тестировании служебных вызовов вызова функции, вы должны проверить результирующую сборку, чтобы убедиться, что функция действительно была (или не была) встроенной. Я не очень хорошо знаком с VC ++, но у него может быть специфичный для компилятора метод принудительного или запрещающего встраивания функции (однако стандартное ключевое слово C ++ inline не будет им).

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

Re: сборка:

; 30   :     const unsigned int maxUINT = -1;
; 31   :     clock_t start = clock();

    mov esi, DWORD PTR __imp__clock
    push    edi
    call    esi
    mov edi, eax

; 32   :     
; 33   :     //============================ NON-INLINE TEST ===============================//
; 34   :     for(unsigned int i = 0; i < maxUINT; ++i)
; 35   :         blank(1.1,2.2,3.3);
; 36   :     
; 37   :     clock_t end = clock();

    call    esi

Это сборка:

  1. Чтение часов
  2. Сохранение значения часов
  3. Чтение часов снова

Заметьте, чего не хватает: вызывая вашу функцию много раз

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

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

1 голос
/ 30 июля 2010

Ваш опубликованный код содержит пару странностей.

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

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

Как и другие предлагали, вам лучше проверить фактический код сборки, сгенерированный компилятором, чтобы определить, действительно ли вы тестируете то, что намеревались.

1 голос
/ 30 июля 2010

Может произойти две вещи:

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

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

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

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

1 голос
/ 30 июля 2010

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

Опубликовать сборку, и мы можем подтвердить ее.

РЕДАКТИРОВАТЬ: прагма компилятора MSVC для запрета встраивания:

#pragma auto_inline(off)
    void myFunction() { 
        // ...
    }
#pragma auto_inline(on)
0 голосов
/ 30 июля 2010

Извините за небольшое пламя ...

Компиляторы думают на языке ассемблера.Ты тоже должен.Что бы вы ни делали, просто переходите по коду на уровне ассемблера.Тогда вы точно будете знать, что сделал компилятор.

Не думайте о производительности в абсолютных терминах, таких как «быстрая» или «медленная».Это все относительно, в процентном отношении.Быстрое создание программного обеспечения происходит путем последовательного удаления ненужных вещей, которые занимают слишком большой процент времени.

Вот в чем пламя: если компилятор может довольно неплохо выполнять встраивание функций, которые явно нуждаются вэто, и если это может сделать действительно хорошую работу по управлению регистрами, я думаю, что это именно то, что он должен делать.Если он может выполнять разумную работу по развертыванию циклов, которые явно могут его использовать, я могу с этим смириться.Если он сбивает себя с толку, пытаясь перехитрить меня, удаляя вызовы функций, которые я ясно написал и намеревался вызывать, или хищнически разбираю свой код, пытаясь сохранить JMP, когда этот JMP занимает 0,000001% времени выполнения (как это делает Fortran), яоткровенно говоря, раздражайтесь.

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

0 голосов
/ 30 июля 2010

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

0 голосов
/ 30 июля 2010

Единственный способ, которым я знаю, чтобы гарантировать функцию, - это встроить #define it

Например:

#define RADTODEG(x) ((x) * 57.29578)

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

0 голосов
/ 30 июля 2010

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

При оптимизации на , однако, компилятор может заметить, что ваша тестовая функция может быть полностью оцененаво время компиляции и раздавить его до «return [constant]» - в этот момент вполне может быть решено встроить обе функции, поскольку они настолько тривиальны, а затем заметить, что циклы бессмысленнытак как значение функции не используется, и сдавите это тоже!Это в основном то, что я получил, когда попробовал.

Так или иначе, вы не проверяете то, что, как вы думали, вы проверяли.


Затраты на вызов функций не те, что использовалисьбыть, по сравнению с накладными расходами на уничтожение кэша команд уровня 1, что агрессивное встраивание делает с вами.Вы можете легко найти в Интернете отчеты о параметре -Os в gcc (оптимизировать для размер ), который является лучшим выбором по умолчанию для больших проектов, чем -O2, и главная причина этого заключается в том, что -O2 много встроенныхболее агрессивно.Я ожидаю, что с MSVC все почти так же.

0 голосов
/ 30 июля 2010

Хм, не должно

//============================ INLINE TEST ===================================//
    for(unsigned int i = 0; i < maxUINT; ++i)
        test(1.1,2.2,3.3);

быть

//============================ INLINE TEST ===================================//
    for(unsigned int i = 0; i < maxUINT; ++i)
         inlineTest(1.1,2.2,3.3);

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

...