Подтвердить что ты не робот

Встроенная функция v. Макрос в C - Что такое служебные данные (память/скорость)?

Я просмотрел Переполнение стека для функций "за" / "против" функций, подобных макросам. встроенные функции.

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

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

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

Существуют ли зависящие от компилятора различия в накладных расходах? У меня есть как 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, получат отличное представление для меня.

4b9b3361

Ответ 1

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

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

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

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

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

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;

Ответ 2

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

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

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

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

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

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

The Crux

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

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

Ответ 3

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

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

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

Ответ 4

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

Ответ 5

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

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

Ответ 6

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

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

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

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

Ответ 7

Макросы, в том числе функциональные макросы, являются простыми текстовыми заменами и, как таковые, могут укусить вас в попке, если вы не очень осторожны с вашими параметрами. Например, очень популярный макрос 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, безусловно, будет кандидатом на оптимизацию).

Ответ 8

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

Я ставлю, что "pow" стоит вам больше, чем любая инкрустация, или макрос спасет вас:)

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

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

Ответ 9

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