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

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

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

Скажем, у меня есть этот код:

void GetSomeData(char* buffer)
{
    // put some data in buffer
}

int main()
{
     char buffer[1024];
     while(1)
     {
          GetSomeData(buffer);
          // do something with the data
     }
     return 0;
}

Можно ли получить какую-либо производительность, если я объявил буфер [1024] глобально?

Я провел несколько тестов в unix через команду time и практически нет различий между временем выполнения.

Но я не уверен...

В теории, должно ли это изменение иметь значение?

4b9b3361

Ответ 1

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

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

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

Во всяком случае, в вашем примере вы сравниваете глобальную переменную с функцией-local (стек/автоматическая) переменная... нет кучи. Память кучи происходит от new или malloc/realloc. Для памяти кучи проблема с производительностью стоит отметить, что сама программа отслеживает, сколько памяти используется, по каким адресам - записи всего, что требуется для обновления в качестве указателей на память, передаются с помощью new/malloc/realloc и еще некоторое время для обновления в качестве указателей delete d или free d.

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

Ответ 2

Цитата из Ответ Джеффа Хилла:

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

enter image description here

Ответ 3

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

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

Ответ 4

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

Ответ 5

Что бы это ни стоило, цикл в приведенном ниже коде - который просто читает и записывает каждый элемент большого массива - последовательно работает на моей машине в 5 раз быстрее, когда массив находится в стеке, а не в куче (GCC, Windows 10, флаг -O3), даже сразу после перезагрузки (когда фрагментация кучи минимизирована):

const int size = 100100100;
int vals[size]; // STACK
// int *vals = new int[size]; // HEAP
startTimer();
for (int i = 1; i < size; ++i) {
    vals[i] = vals[i - 1];
}
stopTimer();
std::cout << vals[size - 1];
// delete[] vals; // HEAP

Конечно, сначала мне пришлось увеличить размер стека до 400 МБ. Обратите внимание, что печать последнего элемента в конце необходима, чтобы компилятор не оптимизировал все.

Ответ 6

В этой теме есть сообщение в блоге, посвященное тестированию стека-выделения-против-кучи-распределения-производительности, в котором показан эталон стратегий распределения. Test написан на C и выполняет сравнение между попытками чистого выделения и выделением памяти init. При разных общих размерах данных выполняется количество циклов и измеряется время. Каждое распределение состоит из 10 различных блоков alloc/init/free с разными размерами (общий размер показан в диаграммах).

Испытания выполняются на процессоре Intel® Core ™ TM i7-6600U, 64-разрядной ОС Linux, 4.15.0-50-generic, исправления Spectre и Meltdown отключены.

Без инициализации: Memory allocation with out data init

С init: Memory allocations with data init

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

При обработке выделенных данных разрыв между производительностью стека и кучи, по-видимому, уменьшается. При 1 МБ циклах malloc/init/free (или выделении стека) с 10 попытками выделения в каждом цикле стек всего на 8% опережает кучу с точки зрения общего времени.

Ответ 7

Предоставление того, что переменные и переменные массивы, объявленные в куче, медленнее, является просто фактом. Подумайте об этом таким образом;

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

Вы когда-нибудь пытались выделить указатель объекта внутри функции? Лучше освободите/удалите его перед тем, как функция выйдет, orelse вы будете иметь memoryleak, дающий то, что вы не делаете этого в объекте класса, где он свободен/удален внутри деконструктора.

Когда дело доходит до доступа к массиву, все они работают одинаково, блок памяти сначала выделяется элементами sizeof (DataType) *. Позже можно получить доступ через →

1 2 3 4 5 6 
^ entry point [0]
      ^ entry point [0]+3