Почему динамическая память это плохо?
На сайтах, наподобие leetcode.com, есть много очень хороших задач и решая их, ты сразу можешь понять насколько эффективна твоя программа в плане памяти и скорости. Но во многих разборах задач я часто встречал советы не использовать динамическую память. Почему?
Ответы (2 шт):
В спортивном программировании не заморачиватются и пишут прямо в задании памяти и столько времени на каждый тесть. Учитывая что, динамика это время на выделение, никто терять его не хочет учитывая что его не хватает. В спортивном программировании нет проверок типа защиты от дурака, введите ещё ... Никто не собирается проверять мощь С с Java примеру. Задача решенная на одном языке должно иметь эквивалент на другом и выбор языка абсолютно не должно иметь значения.
Не занимался спортивным программированием, потому опишу с точки зрения реальных задач. Подозреваю, недоверие к динамической памяти в спортивном программировании идет из С, где было легко получить утечку памяти, а также связано с невозможностью использования сторонних библиотек.
Если говорить о производительности, то нужно уточнить, что сейчас немногие процессоры реализуют аппаратный стек, в большинстве случаев стек - это заранее выделенный кусок динамической памяти с теми же свойствами. В этом плане отличаются матричные процессоры (видеокарты), и некоторые микроконтроллеры, но их рассматривать не будем. Таким образом, на практике вопрос сводится не столько к использованию динамической памяти, сколько к выбору между структурами фиксированного и переменного размера. Если размер задачи известен, ничего не мешает выделить массив фиксированного размера в динамической памяти, компилятор сможет применить те же оптимизации, что и при его создании на стеке (но тут речь идет именно о формальном фиксированном размере, между vector<int> v(32) и new std::array<int, 32>{} есть разница).
У самостоятельного выделения динамической памяти есть два недостатка: относительно низкая скорость выделения (и, возможно, освобождения) и возможность фрагментации (и связанные с этим проблемы локальности данных). Для массивов достаточно большого размера это не актуально - по сравнению с затратами на их обработку, затраты на выделение памяти незначительны.
Проблемы с динамической памятью начинаются при создании множества маленьких объектов. В C++ во многих случаях ничего не мешает объединять несколько малых объектов в большой класс, или массив, и реально проблема возникает при использовании динамических структур данных типа списков, деревьев и графов. И тут задача сводится к выбору подходящих структур и алгоритмов.
К проблеме обычно рекомендуется подходить так: нужно применять самые общие подходы для оптимизации, но сохранение читаемости кода должно быть в приоритете. Необходимость оптимизации должна доказываться профилированием.
Если говорить об удобстве использования, то и для структур фиксированного размера есть удобные интерфейсы. Есть std::array, предлагающий интерфейс С++ для массивов фиксированного размера. Для математических задач есть библиотека Eigen, предоставляющий интерфейсы для матриц фиксированного и динамического размера (и можно выбирать между column major и row major). И есть интересный вариант массивов с фиксированным максимальным размером, но меняющимся реальным (по сути, обертка над обычным массивом фиксированного размера, мешающая обращению к не инициализированному куску массива). Она может быть реализована как vector + fixed_size_allocator.
Еще есть кортежи (tuple), позволяющие объединять в подобие массива фиксированного размера структуры разного типа.
Есть small vector optimisation и small string optimisation, когда массивы/строки малого размера создаются как массив с фиксированным максимальным размером, но при достижении этого максимального размера выделяется динамическая память. Разработчики llvm, например, активно используют такие.
В общем, вопрос не имеет однозначного ответа, для каждой задачи свои решения и на практике у нас не всегда есть возможность выбрать лучшее.