В C использование статических переменных в функции делает это быстрее? - PullRequest
21 голосов
/ 01 октября 2010

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

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

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

Ответы [ 10 ]

24 голосов
/ 01 октября 2010

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

Кроме того, локальные переменные, вероятно, быстрее из-за локальности кэша.

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


Re: расположение кэша ( подробнее здесь ): Часто используемые глобальные переменные, вероятно, имеют временную локальность. Они также могут быть скопированы в регистр во время выполнения функции, но будут записаны обратно в память (кэш) после завершения функции (иначе они не были бы доступны для чего-либо еще; регистры не имеют адресов).

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

10 голосов
/ 01 октября 2010

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

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

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

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

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

4 голосов
/ 01 октября 2010

Абсолютно нет! Единственная разница в «производительности» - это когда инициализируются переменные

    int anint = 42;
 vs
    static int anint = 42;

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

Однако разница настолько незначительна, что едва заметна. Это распространенное заблуждение, что память должна быть выделена для «автоматических» переменных при каждом вызове. Это не так, C использует уже выделенное пространство в стеке для этих переменных.

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

3 голосов
/ 01 октября 2010

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

Рассмотрим две крайности; если у вас есть только одна или несколько локальных переменных, они / они могут легко храниться в регистрах, а не быть выделенными ячейками памяти вообще. Если регистр «давление» достаточно низок, это может произойти без выполнения каких-либо инструкций.

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

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

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

2 голосов
/ 01 октября 2010

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

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

Это усложняется, когда вы вводите указатели, скажем, у вас есть следующий код:

int myFunction()
{
    SomeStruct *A, *B;
    FillOutSomeStruct(B);
    memcpy(A, B, sizeof(A);
    return A.result;
}

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

http://en.wikipedia.org/wiki/Pointer_alias

1 голос
/ 01 октября 2010

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

1 голос
/ 01 октября 2010

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

Функции C создают фрейм стека, в который помещаются переданные параметры, а также помещаются локальные переменные иуказатель возврата обратно туда, где вызывающая сторона вызывала функцию.Здесь нет выделения памяти для управления.Обычно это простое движение указателя и все.Доступ к данным из стека также довольно быстрый.Штрафы, как правило, вступают в игру, когда вы имеете дело с указателями.

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

Основное различие между вашими сценариями - это объем памяти, а не такая большая скорость.

1 голос
/ 01 октября 2010

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

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

Как подсказывает M2tM, запуск профилировщика также является хорошей идеей.Проверьте gprof на тот, который довольно прост в использовании.

0 голосов
/ 01 октября 2010

Профилирование может не видеть разницы, разбирать и знать, что искать, может.

Я подозреваю, что вы получите только вариацию, равную нескольким тактам на цикл (в среднем в зависимости откомпилятор и т. д.).Иногда изменение будет кардинальным улучшением или значительно более медленным, и это не обязательно будет связано с тем, что переменные home перемещены в / из стека.Допустим, вы экономите четыре такта на вызов функции для 10000 вызовов на 2 ГГц процессоре.Очень грубый расчет: сэкономлено 20 микросекунд.20 микросекунд - это много или мало по сравнению с вашим текущим временем выполнения?

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

0 голосов
/ 01 октября 2010

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

...