Любая причина перегрузить глобальное новое и удалить? - PullRequest
53 голосов
/ 20 июля 2009

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

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

Ответы [ 16 ]

76 голосов
/ 01 августа 2009

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

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

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

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

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

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

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

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

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

26 голосов
/ 20 июля 2009

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

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

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

15 голосов
/ 31 июля 2009

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

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

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

Это часто делается в системах, критичных ко времени и пространству, таких как игры. 280Z28, Ми, и Дэн Олсон описали почему.

10 голосов
/ 20 июля 2009

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

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

6 голосов
/ 20 июля 2009

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

4 голосов
/ 31 июля 2009

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

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

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

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

3 голосов
/ 20 июля 2009

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

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

2 голосов
/ 31 июля 2009

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

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

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

2 голосов
/ 31 июля 2009

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

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

2 голосов
/ 31 июля 2009

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

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

...