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

Могут ли статические локальные переменные сократить время выделения памяти?

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

void f(some arguments){
    char buffer[32];
    some operations on buffer;
}

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

4b9b3361

Ответ 1

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

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

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

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

Ответ 2

Нет, это не бесплатное ускорение.

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

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

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

Ответ 3

Увеличение 32 байтов в стеке практически не будет стоить почти во всех системах. Но вы должны проверить это. Контролируйте статическую версию и локальную версию и отправьте сообщение.

Ответ 4

Как это написано сейчас, для распределения нет затрат: 32 байта находятся в стеке. Единственная реальная работа - вам нужно инициализировать нуль.

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

Ответ 5

Я бы предположил, что более общий подход к этой проблеме состоит в том, что если у вас есть функция, называемая много раз, которая нуждается в некоторых локальных переменных, тогда рассмотрите ее оболочку в классе и сделайте эти функции-члены переменных. Подумайте, нужно ли было сделать размер динамическим, поэтому вместо char buffer[32] у вас был std::vector<char> buffer(requiredSize). Это дороже, чем массив для инициализации каждый раз через цикл

class BufferMunger {
public:
   BufferMunger() {};
   void DoFunction(args);
private:
   char buffer[32];
};

BufferMunger m;
for (int i=0; i<1000; i++) {
   m.DoFunction(arg[i]);  // only one allocation of buffer
}

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

Ответ 6

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

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

Ответ 7

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

Пример первого: на Z80 код для установки фрейма стека для функции с любыми локальными переменными был довольно длинным. Кроме того, код для доступа к локальным переменным был ограничен использованием режима адресации (IX + d), который был доступен только для 8-битных инструкций. Если X и Y являются глобальными/статическими или обе локальными переменными, утверждение "X = Y" может быть собрано как:

; If both are static or global: 6 bytes; 32 cycles
  ld HL,(_Y) ; 16 cycles
  ld (_X),HL ; 16 cycles
; If both are local: 12 bytes; 56 cycles
  ld E,(IX+_Y)   ; 14 cycles
  ld D,(IX+_Y+1) ; 14 cycles
  ld (IX+_X),D   ; 14 cycles
  ld (IX+_X+1),E ; 14 cycles

100% штрафного кода и 75% штрафного времени в дополнение к коду и времени для установки кадра стека!

В процессоре ARM одна команда может загрузить переменную, которая находится в пределах +/- 2K указателя адреса. Если функция локальных переменных составляет всего 2 К или меньше, к ним может быть доступна одна инструкция. Глобальные переменные обычно требуют загрузки двух или более инструкций, в зависимости от того, где они хранятся.

Ответ 8

С gcc, я вижу некоторое ускорение:

void f() {
    char buffer[4096];
}

int main() {
    int i;
    for (i = 0; i < 100000000; ++i) {
        f();
    }
}

И время:

$ time ./a.out

real    0m0.453s
user    0m0.450s
sys  0m0.010s

изменение буфера на статический:

$ time ./a.out

real    0m0.352s
user    0m0.360s
sys  0m0.000s

Ответ 9

В зависимости от того, что именно делает переменная и как ее используют, ускорение почти ничего не значит. Поскольку (на системах x86) стек стека выделяется для всех локальных варов одновременно с простой единичной func (sub esp, amount), таким образом, имея только один другой стек var, исключает любое усиление. единственным исключением из этого является действительно огромные буферы, и в этом случае компилятор может вставить в _chkstk для выделения памяти (но если ваш буфер такой большой, вы должны переоценить свой код). Компилятор не может превратить стекную память в статическую память с помощью оптимизации, так как не может предположить, что функция будет использоваться в одном потоковом окружении, плюс она будет работать с конструкторами объектов и деструкторами и т.д.

Ответ 10

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

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

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

Ответ 11

В большинстве случаев функция будет существенно медленнее. Это связано с тем, что сегмент статических данных не находится рядом с стеком, и вы потеряете согласованность кеша, поэтому при попытке доступа к нему вы получите пропущенный кеш. Однако при распределении регулярного char [32] в стеке он находится рядом со всеми вашими необходимыми данными и очень мало подходит для доступа. Стоимость инициализации массива на основе стека char не имеет смысла.

Это игнорируется, что статика имеет много других проблем.

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