Встроенная функция v. Макрос в C - Что такое издержки (память / скорость)? - PullRequest
32 голосов
/ 08 марта 2011

Я искал Переполнение стека , чтобы узнать о плюсах и минусах функциональных макросов и встроенных функций.

Я нашел следующее обсуждение: Плюсы и минусы различных макрофункций / встроенных методов в C

... но он не ответил на мой первоочередный вопрос.

А именно, каковы издержки использования макрофункции (с переменными, возможно, вызовами других функций) в c с точки зрения использования памяти и скорости выполнения?

Существуют ли какие-либо различия в накладных расходах, зависящие от компилятора? В моем распоряжении и icc, и gcc.

Мой фрагмент кода, который я модулирую:

double AttractiveTerm = pow(SigmaSquared/RadialDistanceSquared,3);
double RepulsiveTerm = AttractiveTerm * AttractiveTerm;
EnergyContribution += 
   4 * Epsilon * (RepulsiveTerm - AttractiveTerm);

Моя причина для превращения его во встроенную функцию / макрос заключается в том, что я могу поместить его в файл c, а затем условно скомпилировать другие похожие, но немного другие функции / макросы.

например:.

double AttractiveTerm = pow(SigmaSquared/RadialDistanceSquared,3);
double RepulsiveTerm = pow(SigmaSquared/RadialDistanceSquared,9);
EnergyContribution += 
   4 * Epsilon * (RepulsiveTerm - AttractiveTerm);

(обратите внимание на разницу во второй строке ...)

Эта функция является центральной в моем коде и вызывается тысячи раз за шаг в моей программе, а моя программа выполняет миллионы шагов. Таким образом, я хочу, чтобы ПОСЛЕДНИЕ издержки были возможны, поэтому я трачу время на беспокойство по поводу затрат на встраивание v. Преобразования кода в макрос.

Основываясь на предыдущем обсуждении, я уже осознал другие плюсы и минусы (независимость типов и возникающие в результате ошибки) макросов ... но больше всего я хочу знать, и в настоящее время не знаю, это ЭФФЕКТИВНОСТЬ.

Я знаю, что у некоторых из вас, ветеранов C, будет отличное понимание для меня !!

Ответы [ 9 ]

23 голосов
/ 08 марта 2011

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

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

Скорее всего, макрос будет нести такие издержки (хотя, опять же, мало что мешает компилятору каким-либо образом что-то делать; стандарт не определяет, к чему должны расширяться программы машинного кода, только наблюдаемое поведение скомпилированной программы. ).

Используйте все, что чище. Профиль. Если это имеет значение, сделайте что-то другое.

Кроме того, что сказал fizzer ; вызовы pow (и Division) обычно дороже, чем служебные вызовы. Минимизация их - хорошее начало:

double ratio = SigmaSquared/RadialDistanceSquared;
double AttractiveTerm = ratio*ratio*ratio;
EnergyContribution += 4 * Epsilon * AttractiveTerm * (AttractiveTerm - 1.0);

EnergyContribution состоит только из терминов, которые выглядят следующим образом? Если это так, вытащите 4 * Epsilon и сохраните два умножения на итерацию:

double ratio = SigmaSquared/RadialDistanceSquared;
double AttractiveTerm = ratio*ratio*ratio;
EnergyContribution += AttractiveTerm * (AttractiveTerm - 1.0);
// later, once you've done all of those terms...
EnergyContribution *= 4 * Epsilon;
10 голосов
/ 08 марта 2011

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

Функция, встроенная или иным образом известная компилятору, и может принимать решения о том, что с ней делать. Ключевое слово inline, предложенное пользователем, является всего лишь предложением, и компилятор может переопределить его. Именно это переопределение в большинстве случаев приведет к улучшению кода.

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

Макросы очень мощные, и в подтверждение этого я бы привел google test и google mock. Есть много причин использовать макросы: D.

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

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

The Crux

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

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

8 голосов
/ 08 марта 2011

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

inline double cube(double x)
{
    return x * x * x;
}

- единственное, что существенно повлияет на вашу производительность.

3 голосов
/ 11 октября 2012

Пожалуйста, ознакомьтесь со стандартом кодирования CERT Secure, в котором говорится о макросах и встроенных функциях с точки зрения безопасности и выявления ошибок. Я не рекомендую использовать макросы, подобные функциям, потому что: - Меньше Профилирования - Менее прослеживаемый - Сложнее отлаживать - Может привести к серьезным ошибкам

2 голосов
/ 08 марта 2011

Если вы случайная пауза , вероятно, вы увидите, что 100% (минус эпсилон) времени находится внутри функции pow, поэтому то, как она туда попала, нет разницы.

Предполагая, что вы найдете это, первое, что нужно сделать, это избавиться от вызовов pow, которые вы нашли в стеке.(Как правило, он принимает log первого аргумента, умножает его на второй аргумент и exp этого, или что-то, что делает то же самое. log и exp вполне могутбыть сделано в какой-то серии, включающей много арифметики. Конечно, она ищет особые случаи, но это все равно займет больше времени, чем вы.) Одно это должно дать вам примерно ускорение на порядок.

Затем снова сделайте случайную паузу.Теперь вы увидите, что что-то еще занимает много времени.Я не могу догадаться, что это будет, и никто другой не может, но вы, вероятно, тоже можете уменьшить это.Просто продолжайте делать это до тех пор, пока вы не сможете больше.

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

2 голосов
/ 08 марта 2011

Макросы, в том числе функционально-подобные макросы, являются простыми текстовыми подстановками, и, как таковые, могут укусить вас в задницу, если вы не действительно осторожны со своими параметрами.Например, очень популярный макрос SQUARE:

#define SQUARE(x) ((x)*(x))

может быть катастрофическим ожиданием, если вы назовете его SQUARE(i++).Кроме того, функционально-подобные макросы не имеют понятия области видимости и не поддерживают локальные переменные;самый популярный хак - это что-то вроде

#define MACRO(S,R,E,C)                                     \
do                                                         \   
{                                                          \
  double AttractiveTerm = pow((S)/(R),3);                  \
  double RepulsiveTerm = AttractiveTerm * AttractiveTerm;  \
  (C) = 4 * (E) * (RepulsiveTerm - AttractiveTerm);        \
} while(0)

, что, конечно, затрудняет назначение результата, например x = MACRO(a,b);.

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

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

2 голосов
/ 08 марта 2011

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

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

0 голосов
/ 26 марта 2011

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

0 голосов
/ 08 марта 2011

Как уже говорили другие, это в основном зависит от компилятора.

Бьюсь об заклад, "pow" стоит вам больше, чем любая вставка или макрос спасет вас:)

Я думаю, что его чище, еслиэто встроенная функция, а не макрос.

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

...