Динамическое распределение хранит данные в случайном месте в куче? - PullRequest
3 голосов
/ 16 марта 2019

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

, но когда я динамически выделяю переменную в памяти кучи в c ++, как это.

int * a = new int{1};
int * a2 = new int{2};
int * a3 = new int{3};
int * a4 = new int{4};

Вопрос 1:эти переменные хранятся в смежном месте памятиВопрос 2: если нет, то потому что динамическое распределение хранит переменные в случайном месте в куче памяти?Вопрос3: так ли динамическое распределение увеличивает вероятность пропадания кэша и имеет низкую пространственную локальность?

Ответы [ 3 ]

1 голос
/ 16 марта 2019

Часть 1: Являются ли отдельные распределения смежными?

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

Различные реализации c ++ используют разные алгоритмы для определения того, как распределена память.

Часть 2. Является ли распределение случайным?

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

Распределение происходит в два этапа:

  1. Распределитель запрашивает большой кусок памяти у ОС
  2. Берут фрагменты этого большого куска и возвращают их всякий раз, когда вы вызываете new, пока вы не запросите больше памяти, чем нужно, и в этом случае он запросит другой большой кусок из ОС.

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

Часть 3: как избежать ошибок в кэше

Если ошибки в кеше являются узким местом в вашем коде,

  • Постарайтесь уменьшить количество косвенных ссылок (сохраняя объекты в массивах по значению, а не по указателю);
  • Убедитесь, что память, с которой вы работаете, является смежной, насколько позволяет дизайн (поэтому используйте std :: array или std :: vector вместо связанного списка и предпочитайте несколько больших выделений множеству маленьких ); и
  • Постарайтесь спроектировать алгоритм так, чтобы он как можно меньше перепрыгивал в памяти.

Хороший общий принцип - просто использовать std :: vector объектов, если у вас нет веских причин использовать что-то более причудливое. Поскольку они имеют лучшую локальность кэша, std :: vector быстрее вставляет и удаляет элементы, чем std :: list, даже до десятков или даже сотен элементов.

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

  • Предпочитают использовать MyClass x{}; вместо MyClass* x = new MyClass{}; и
  • Предпочитают std::vector<MyClass> вместо std::vector<MyClass*>.

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

0 голосов
/ 16 марта 2019

Короче говоря:

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

  2. совсем не (AFAIK, алгоритмы кучи являются детерминированными без какой-либо случайности),

  3. обычно да (например, здесь может помочь пул памяти).

0 голосов
/ 16 марта 2019

ИМХО, это специфическая реализация операционной системы / стандартной библиотеки C ++.

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

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

...