Зачем использовать произвольное динамическое выделение памяти поверх стека - PullRequest
0 голосов
/ 11 июля 2019

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

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


Пример. Если Dog является классом, то вместо того, чтобы просто объявить Dog puppy;, они хотят, чтобы мы сделали

Dog* puppy = nullptr; 
custom_alloc(puppy);
new(puppy) Dog(); // the constructor
// do stuff
puppy->~Dog(); // the destructor
custom_free(puppy)

Реальная функция custom_alloc нам неизвестна. Чтобы запустить программу, данная функция custom_alloc должна быть оболочкой malloc. И custom_free будет оболочкой free

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

1 Ответ

1 голос
/ 11 июля 2019

Возможные причины:

  1. Размер стека ограничен; в то время как типичные библиотеки потоков выделяют 1–10 МБ для стека каждого потока, весьма обычно, когда ограничение устанавливается ниже для приложений, в которых сотни или тысячи потоков должны запускаться одновременно (например, веб-серверы с высоким трафиком; Microsoft IIS использовал для использования Ограничение 256 КБ, и только увеличено до 512 КБ для 64-битных установок).

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

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

  4. Постоянное хранилище: распределитель может быть поддержан файлом сопоставленной памяти; для структурированных данных этот файл может удвоиться как длительное хранение

  5. Производительность: Известно, что пользовательские распределители (например, TBB Intel) значительно сокращают время выполнения при определенных обстоятельствах . Это больше оправдывает использование пользовательского распределителя вместо распределителя по умолчанию; Пользовательские распределители обычно не бьют стековое хранилище (за исключением действительно нишевых случаев, когда локальность памяти может быть улучшена путем удаления больших объектов из стека и помещения их в свое собственное выделенное хранилище).

  6. (Вероятно, ужасная идея) Избегание накладных расходов на обработку исключений. Если ваши классы RAII, то должен быть сгенерирован код, чтобы очистить их по различным путям кода в случае исключения. Сырые указатели не генерируют такой код. Конечно, если вы не принимаете меры для очистки исключений самостоятельно, это означает утечку памяти, но в редких случаях (например, когда вы ожидаете, что программа полностью завершится и вы хотите, чтобы ОС выполняла очистку памяти), это может привести к предоставить незначительную «выгоду».

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

Все, что сказано, их подход к этому довольно ужасен; требуется ручное размещение new, а вызов деструктора неприятен (std::unique_ptr / std::shared_ptr может немного помочь, предоставляя пользовательские функторы удаления, которые делают эту работу за вас, но это уродливо, даже если это так). Обычно, если вам нужен пользовательский распределитель, вы должны определить соответствующие перегрузки для operator new / operator delete. Таким образом, избежать выделения стека (по любой причине) не так уж неприятно; вы просто заменяете логически расположенные в стеке переменные на std::unique_ptr s (созданные с помощью std::make_unique), и ваш код остается довольно простым.

...