C ++ / VS2008: производительность макросов и встроенных функций - PullRequest
2 голосов
/ 28 сентября 2010

All

Я пишу некоторый чувствительный к производительности код, в том числе трехмерный векторный класс, который будет выполнять множество перекрестных продуктов. Как давний программист на 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);</p> <pre><code>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 тиков, что лучше, но все же не то же самое.

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

Ответы [ 4 ]

12 голосов
/ 28 сентября 2010

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

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

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

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;

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

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 голосов
/ 28 сентября 2010

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

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

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

0 голосов
/ 28 сентября 2010

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

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

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, но это все еще не исправит все)

0 голосов
/ 28 сентября 2010

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

...