2010-09-28 4 views
2

Все,C++/VS2008: Выполнение макросов против функций Инлайн

Я пишу некоторые производительности чувствительную кода, в том числе 3D-вектор класса, который будет делать много перекрестных продуктов. Являясь давним программистом на C++, я знаю все о пороках макросов и различных преимуществах встроенных функций. У меня давно сложилось впечатление, что встроенные функции должны быть примерно такой же скорости, что и макросы. Тем не менее, при проверке производительности макросов и встроенных функций я пришел к интересному открытию, которое, я надеюсь, является результатом того, что я делаю глупую ошибку где-то: макро версия моей функции, по-видимому, более чем в 8 раз быстрее, чем встроенная версия !

Во-первых, смешно урезана версия простого векторного класса:

 
class Vector3d 
{ 
public: 
    double m_tX, m_tY, m_tZ; 

    Vector3d() : m_tX(0), m_tY(0), m_tZ(0) {} 
    Vector3d(const double &tX, const double &tY, const double &tZ): 
     m_tX(tX), m_tY(tY), m_tZ(tZ) {} 

    static inline void CrossAndAssign (const Vector3d& cV1, const Vector3d& cV2, Vector3d& cV) 
    { 
     cV.m_tX = cV1.m_tY * cV2.m_tZ - cV1.m_tZ * cV2.m_tY; 
     cV.m_tY = cV1.m_tZ * cV2.m_tX - cV1.m_tX * cV2.m_tZ; 
     cV.m_tZ = cV1.m_tX * cV2.m_tY - cV1.m_tY * cV2.m_tX; 
    } 

#define FastVectorCrossAndAssign(cV1,cV2,cVOut) { \ 
    cVOut.m_tX = cV1.m_tY * cV2.m_tZ - cV1.m_tZ * cV2.m_tY; \ 
    cVOut.m_tY = cV1.m_tZ * cV2.m_tX - cV1.m_tX * cV2.m_tZ; \ 
    cVOut.m_tZ = cV1.m_tX * cV2.m_tY - cV1.m_tY * cV2.m_tX; } 
}; 

Вот мой пример кода бенчмаркинг:

Vector3d right; Vector3d forward(1.0, 2.2, 3.6); Vector3d up(3.2, 1.4, 23.6);

clock_t start = clock(); 
for (long l=0; l < 100000000; l++) 
{ 
    Vector3d::CrossAndAssign(forward, up, right); // static inline version 
} 

clock_t end = clock(); 
std::cout << end - start << endl; 


clock_t start2 = clock(); 
for (long l=0; l<100000000; l++) 
{ 
    FastVectorCrossAndAssign(forward, up, right); // macro version 
} 
clock_t end2 = clock(); 

std::cout << end2 - start2 << endl; 

Конечный результат: При оптимизации полностью выключен, то встроенная версия занимает 3200 тиков, а макро версия 500 тикает ... С включенной оптимизацией (/ O2, максимальной скоростью и другими настройками скорости) я могу получить встроенную версию до 1100 тиков, что лучше, но все же не t он такой же.

Итак, я призываю всех вас: это действительно так? Я где-то совершил глупую ошибку? Или встроенные функции действительно намного медленнее - и если да, то почему?

+1

Да, изменения кода и не проверяя, что она производит тот же результат, мать глупых ошибок. –

+0

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

+2

«Когда оптимизация полностью отключена, встроенная версия занимает [больше]». Ну, что вы ожидаете, когда выключаете вставку? – sbi

ответ

0

обратите внимание, что если вы используете ключевое слово inline, это всего лишь подсказка для компилятора. Если вы отключите оптимизацию, это может привести к тому, что компилятор не включит функцию. Вы должны перейти в Настройки проекта/C++/Оптимизация/и включите оптимизацию. Какие настройки вы использовали для «расширения встроенных функций»?

+0

Включение полной оптимизации, обе мои функции возвращают время 0, поэтому я подозреваю, что все циклы оптимизированы, потому что они ничего не приносят. Мне придется поиграть с этим еще немного. – Tarindel

+0

вы можете получить доступ к результатам (например, добавить все результаты), а затем вывести окончательную сумму или что-то в этом роде. – Philipp

0

Это также зависит от настроек оптимизации и компилятора. также ищите поддержку вашего компилятора для всегда встроенного объявления inline/force. inlining является так же быстро, как макрос.

По умолчанию ключевое слово представляет собой подсказку-строку inline/always inline (по большей части) возвращает элемент управления программисту исходного намерения ключевого слова.

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

0

Кроме того, что Филипп упомянул, если вы используете MSVC, вы можете использовать __forceinline или эквивалент gcc __attrib__, чтобы исправить зонды с помощью inlining.

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

FastVectorCrossAndAssign(getForward(), up, right); 

он будет расширяться:

right.m_tX = getForward().m_tY * up.m_tZ - getForward().m_tZ * up.m_tY; 
right.m_tY = getForward().m_tZ * up.m_tX - getForward().m_tX * up.m_tZ; 
right.m_tZ = getForward().m_tX * up.m_tY - getForward().m_tY * up.m_tX; 

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

12

ПРИМЕЧАНИЯ: После публикации этого ответа, оригинальный вопрос был отредактирован, чтобы удалить эту проблему. Я оставлю ответ, поскольку он поучителен на нескольких уровнях.

Петли отличаются тем, что они делают!

если вручную разворачивать макрос, мы получаем:

for (long l=0; l<100000000; l++) 
    right.m_tX = forward.m_tY * up.m_tZ - forward.m_tZ * up.m_tY; 
    right.m_tY = forward.m_tZ * up.m_tX - forward.m_tX * up.m_tZ; 
    right.m_tZ = forward.m_tX * up.m_tY - forward.m_tY * up.m_tX; 

Обратите внимание на absense фигурные скобки. Поэтому компилятор видит это как:

for (long l=0; l<100000000; l++) 
{ 
    right.m_tX = forward.m_tY * up.m_tZ - forward.m_tZ * up.m_tY; 
} 
right.m_tY = forward.m_tZ * up.m_tX - forward.m_tX * up.m_tZ; 
right.m_tZ = forward.m_tX * up.m_tY - forward.m_tY * up.m_tX; 

Это делает его очевидным, почему второй цикл настолько быстр.

Udpate: Это также хороший пример того, почему макросы зло :)

+0

О, ** спасибо ** за то, что вы дали этому ** прекрасный пример **, почему нужно всегда ** использовать брекеты даже для однострочных тел. Абсолютно +1, орел глаз! – DevSolar

+0

Я бы не сказал, что макросы являются злыми * per se *. Они укусят вас, когда вы небрежны (например, не обертываете многострочный макрос в 'do {...} while (0)'). – DevSolar

+0

@DevSolar: зачем переносить макрос в 'do {...} while (0)' когда '{...}' работает отлично? Это важно, чтобы заставить пользователя положить после нее двоеточие? –

Смежные вопросы