Любая причина для перегрузки глобального нового и удаления?

Если вы не программируете части операционной системы или встроенной системы, есть ли причины для этого? Я могу себе представить, что для некоторых определенных классов, которые создаются и уничтожаются, часто перегрузка функций управления памятью или введение пула объектов может снизить накладные расходы, но делать эти вещи глобально?

Добавление
Я только что нашел ошибку в перегруженной функции удаления - память не всегда была освобождена. И это было в не столь важном для памяти приложении. Кроме того, отключение этих перегрузок снижает производительность только на 0,5%.

Ответ 1

Мы перегружаем глобальные новые и удаляем операторы, где я работаю по многим причинам:

  • объединение всех небольших распределений - снижает накладные расходы, уменьшает фрагментацию, может увеличить производительность приложений с малым распределением ресурсов
  • кадрирование с известным временем жизни - игнорируйте все освобождения до самого конца этого периода, затем освобождайте их все вместе (по общему признанию, мы делаем это больше с локальными перегрузками операторов, чем глобальными)
  • выравнивание - к границам кешлайн и т.д.
  • alloc fill - помогает выявить использование неинициализированных переменных
  • бесплатное заполнение - помощь в обнаружении использования ранее удаленной памяти
  • с задержкой - повышение эффективности бесплатного заполнения, иногда увеличение производительности
  • часовые или fenceposts - помогает выявлять переполнения буфера, недоработки и случайный указатель дикой природы
  • перенаправление распределений - для учета NUMA, специальных областей памяти или даже для разделения отдельных систем в памяти (например, встроенных языков сценариев или DSL)
  • сбор мусора или очистка - снова полезно для тех встроенных языков сценариев
  • проверка кучи - вы можете пройти через структуру данных кучи каждый N allocs/frees, чтобы убедиться, что все выглядит нормально
  • учет, включая отслеживание утечки и снимки использования/статистика (стеки, возраст распределения и т.д.)

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

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

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

Взгляните на некоторые распределители и системы отладки, которые есть там для C/С++, и вы быстро придумаете эти и другие идеи:

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

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

Есть ситуации, когда это быстрее, проще, меньше деловой/юридической суеты, пока ничего доступного для вашей платформы, или просто более поучительно: выкопайте и напишите глобальную перегрузку.

Ответ 2

Наиболее распространенная причина перегрузки new и delete - просто проверить утечки памяти и статистику использования памяти. Обратите внимание, что "утечка памяти" обычно обобщается на ошибки памяти. Вы можете проверить такие вещи, как двойные удаления и переполнение буфера.

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

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

Ответ 3

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

Например, у вас может быть серия пулов памяти с фиксированными размерами блоков. Переопределение глобального new позволяет направлять все 61-байтные распределения, скажем, в пул с 64-байтовыми блоками, все байт 768-1024 выделяет пул 1024b-блоков, все те, что указаны выше, для пула блоков байтов 2048, и что-то большее, чем 8kb, к общей рваной куче.

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

Это часто делается в системах с критическими по времени и пространству, например играми. 280Z28, Meeh и Dan Olson описали, почему.

Ответ 4

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

Изменить: для моего собственного кода я бы сделал это только в крайнем случае. И я имею в виду, что почти никогда не буду использовать его. Но мои личные проекты, очевидно, намного меньше/очень разные требования.

Ответ 5

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

Ответ 6

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

Вы также можете использовать его для установки защитных полос вокруг выделенной памяти. Если/при сбое приложения, вы можете посмотреть адрес. Если вы видите содержимое как "0xABCDABCD" (или независимо от того, что вы выбираете в качестве охраны), вы получаете доступ к памяти, которой вы не являетесь.

Возможно, после вызова delete вы можете заполнить это пространство аналогично узнаваемым шаблоном. Я считаю, VisualStudio делает что-то подобное в отладке. Не заполняет ли он неинициализированную память 0xCDCDCDCD?

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

Ответ 7

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

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

Ответ 8

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

В linux вы можете поместить свою собственную версию malloc вместо системы, как в этом примере:

http://developers.sun.com/solaris/articles/lib_interposers.html

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

Поскольку вы делаете это в общей библиотеке с LD_PRELOAD, вам даже не нужно перекомпилировать ваше приложение.

Ответ 9

Я видел, как это делалось в системе, для которой для "безопасности" * были необходимы причины для записи по всей памяти, используемой при де-распределении. Подход состоял в том, чтобы выделить несколько байтов в начале каждого блока памяти, который будет содержать размер общего блока, который затем будет перезаписан нулями при удалении.

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

Конечно, не говорю, что это хорошее применение, но это, вероятно, один из самых творческих...

* К сожалению, речь шла не столько о реальной безопасности, сколько о появлении безопасности...

Ответ 10

Плагины Photoshop, написанные на С++, должны переопределять operator new, чтобы они получали память через Photoshop.

Ответ 11

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

Но 99% времени, которое оно выполняло как функцию отладки, регистрировали, как часто, где, когда память выделяется и освобождается.

Ответ 12

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

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

Ответ 13

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

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

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

Ответ 14

Наиболее распространенным случаем использования является проверка утечки.

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

Ответ 15

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

Вот довольно хорошее сообщение о одном из способов сделать это и некоторые аргументы.

Ответ 16

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