Мне действительно нравится управление памятью на основе области (SBMM) или RAII , так как на него чаще всего (сбивает с толку?) Ссылается сообщество C ++. Насколько я знаю, за исключением C ++ (и C), сегодня нет другого основного языка, который бы использовал SBMM / RAII в качестве основного механизма управления памятью, и вместо этого они предпочитают использовать сборку мусора (GC).
Я нахожу это довольно запутанным, так как
- SBMM делает программы более детерминированными (вы можете точно сказать, когда объект уничтожен);
- на языках, которые используют GC, вам часто приходится выполнять ручное управление ресурсами (см., например, закрытие файлов в Java), что частично противоречит цели GC и также подвержено ошибкам;
- Кучи памяти также (очень элегантно, imo) могут быть ограничены областью видимости (см.
std::shared_ptr
в C ++).
Почему СБММ не используется более широко? Каковы его недостатки?
finalize()
метод объекта будет вызван перед сборкой мусора. По сути, это создает тот же класс проблем, который должен решать сборщик мусора.Ответы:
Давайте начнем с того, что постулируем, что память (в десятки, сотни или даже тысячи раз) более распространена, чем все другие ресурсы вместе взятые. Каждая переменная, объект, член объекта нуждается в некоторой памяти, выделенной для него и освобожденной позже. Для каждого файла, который вы открываете, вы создаете от десятков до миллионов объектов для хранения данных, извлеченных из файла. Каждый поток TCP идет вместе с неограниченным количеством временных строк байтов, созданных для записи в поток. Мы здесь на одной странице? Отлично.
Чтобы RAII работал (даже если у вас есть готовые умные указатели для каждого варианта использования под солнцем), вам необходимо получить право собственности . Вам нужно проанализировать, кому должен принадлежать тот или иной объект, а кому нет, и когда право собственности должно быть передано от А к Б. Конечно, вы можете использовать общее владение всем , но тогда вы будете эмулировать GC с помощью интеллектуальных указателей. В этот момент становится намного проще и быстрее встроить GC в язык.
Сборка мусора освобождает вас от этой заботы о наиболее часто используемом ресурсе - памяти. Конечно, вам все равно нужно принять такое же решение для других ресурсов, но они гораздо реже (см. Выше), а сложное (например, совместное) владение тоже менее распространено. Умственная нагрузка значительно снижается.
Теперь вы назвали некоторые недостатки создания мусора для всех значений. Тем не менее, интегрировать и RA-безопасные GC, и типы значений с RAII в один язык чрезвычайно сложно, поэтому, возможно, лучше перенести эти компромиссы другими способами?
Потеря детерминизма на практике оказывается не такой уж и плохой, потому что она влияет только на время жизни детерминированного объекта . Как описано в следующем параграфе, большинство ресурсов (кроме памяти, которая является обильной и может быть переработана довольно лениво) не связаны с временем жизни объекта в этих языках. Есть несколько других вариантов использования, но они редки в моем опыте.
Ваш второй пункт, ручное управление ресурсами, в настоящее время решается с помощью оператора, который выполняет очистку на основе области действия, но не связывает эту очистку с временем жизни объекта (следовательно, не взаимодействует с ГХ и безопасностью памяти). Это
using
в C #,with
в Python,try
-with-resources в последних версиях Java.источник
using
заявления возможны только локально. Таким способом невозможно очистить ресурсы, содержащиеся в переменных-членах.using
это шутка по сравнению с RAII, просто чтобы вы знали.RAII также следует из автоматического управления памятью подсчета ссылок, например, используемого Perl. Хотя подсчет ссылок прост в реализации, детерминистичен и достаточно производителен, он не может справиться с циклическими ссылками (они вызывают утечку), поэтому он обычно не используется.
Языки со сборщиком мусора не могут использовать RAII напрямую, но часто предлагают синтаксис с эквивалентным эффектом. В Java у нас есть оператор try-with-ressource
который автоматически вызывает
.close()
ресурс при выходе из блока. C # имеетIDisposable
интерфейс, который позволяет.Dispose()
вызываться при выходе изusing (...) { ... }
оператора. Python имеетwith
утверждение:который работает аналогичным образом. Интересно отметить, что метод открытия файлов в Ruby получает обратный вызов. После выполнения обратного вызова файл закрывается.
Я думаю, что Node.js использует ту же стратегию.
источник
with-open-filehandle
функции, которые открывают файл , передают его функции и по возвращении функции закрывают файл снова.На мой взгляд, наиболее убедительным преимуществом сборки мусора является то, что он обеспечивает возможность компоновки . Правильность управления памятью является локальным свойством в среде сбора мусора. Вы можете посмотреть на каждую часть изолированно и определить, может ли она утечь память. Объедините любое количество частей, исправляющих память, и они останутся правильными.
Когда вы полагаетесь на подсчет ссылок, вы теряете это свойство. Возможность утечки памяти в вашем приложении становится общим свойством всего приложения с подсчетом ссылок. Каждое новое взаимодействие между частями имеет возможность использовать неправильное владение и нарушать управление памятью.
Это оказывает очень заметное влияние на дизайн программ на разных языках. Программы на языках GC, как правило, представляют собой нечто большее, чем супы объектов с большим количеством взаимодействий, в то время как в языках без GC предпочитают структурированные части со строго контролируемыми и ограниченными взаимодействиями между ними.
источник
Закрытия являются важной характеристикой практически всех современных языков. Их очень легко реализовать с помощью GC, и очень трудно (хотя и не невозможно) правильно понять с RAII, поскольку одна из их основных функций заключается в том, что они позволяют вам абстрагироваться в течение жизни ваших переменных!
C ++ получил их только через 40 лет после всех остальных, и многие умные люди потратили много усилий, чтобы понять их правильно. Напротив, многие языки сценариев, разработанные и реализованные людьми с нулевыми знаниями в разработке и реализации языков программирования, имеют их.
источник
[&]
синтаксис. Любой программист C ++ уже связывает&
знак со ссылками и знает о устаревших ссылках.Для большинства программистов ОС является недетерминированной, их распределитель памяти недетерминирован, и большинство программ, которые они пишут, являются параллельными и, следовательно, по своей сути недетерминированными. Добавление ограничения на то, что деструктор вызывается именно в конце области, а не чуть раньше или чуть позже, не является значительным практическим преимуществом для подавляющего большинства программистов.
Смотрите
using
в C # иuse
в F #.Другими словами, вы можете взять кучу, которая является решением общего назначения, и изменить ее так, чтобы она работала только в конкретном случае, который серьезно ограничивает. Это правда, конечно, но бесполезно.
SBMM ограничивает то, что вы можете сделать:
SBMM создает восходящую проблему funarg с первоклассными лексическими замыканиями, поэтому замыкания популярны и просты в использовании в таких языках, как C #, но редки и сложны в C ++. Обратите внимание, что существует общая тенденция использования функциональных конструкций в программировании.
SBMM требует деструкторов, и они препятствуют вызовам хвоста, добавляя больше работы, прежде чем функция сможет вернуться. Хвостовые вызовы полезны для расширяемых конечных автоматов и предоставляются такими вещами, как .NET.
Известно, что некоторые структуры данных и алгоритмы сложно реализовать с помощью SBMM. В основном везде, где эти циклы происходят естественным образом. Наиболее заметно графовые алгоритмы. Вы фактически заканчиваете тем, что написали свой собственный GC.
Параллельное программирование сложнее, потому что поток управления и, следовательно, время жизни объекта здесь по своей природе недетерминировано. Практическими решениями в системах передачи сообщений, как правило, являются глубокое копирование сообщений и использование чрезмерно долгого срока службы.
SBMM поддерживает объекты живыми до конца их области видимости в исходном коде, которая часто длиннее, чем необходимо, и может быть намного дольше, чем необходимо. Это увеличивает количество плавающего мусора (недоступные объекты, ожидающие повторного использования). Напротив, отслеживание сборки мусора приводит к освобождению объектов вскоре после того, как исчезает последняя ссылка на них, что может быть гораздо раньше. См. Мифы управления памятью: оперативность .
SBMM настолько ограничивает, что программистам нужен путь эвакуации в ситуациях, когда нельзя вкладывать время жизни в гнездо. В C ++
shared_ptr
предлагает маршрут эвакуации, но он может быть в ~ 10 раз медленнее, чем отслеживание сборки мусора . Таким образом, использование SBMM вместо GC в большинстве случаев поставило бы большинство людей в тупик. Это не значит, однако, что это бесполезно. SBMM по-прежнему имеет ценность в контексте систем и встроенного программирования, где ресурсы ограничены.FWIW вы могли бы проверить Forth и Ada, и прочитать о работе Николаса Вирта.
источник
shared_ptr
это редко встречается в C ++, потому что он такой медленный. Во-вторых, это сравнение яблок и апельсинов (как я уже цитировал в статье), потому чтоshared_ptr
оно во много раз медленнее, чем производственный сборщик мусора. В-третьих, ГХ не являются вездесущими и их избегают в таких программах, как LMax и FIX-движок Rapid Addition.Глядя на некоторый индекс популярности, такой как TIOBE (который, разумеется, спорен, но я полагаю, что для такого рода вопросов вполне нормально использовать это), вы сначала видите, что ~ 50% из 20 лучших из них - "языки сценариев" или "диалекты SQL" ", где" простота использования "и средства абстракции имеют гораздо большее значение, чем детерминированное поведение. Из оставшихся «скомпилированных» языков есть около 50% языков с SBMM и ~ 50% без. Поэтому, когда вы убираете языки сценариев из своих расчетов, я бы сказал, что ваше предположение неверно, среди скомпилированных языков те, что с SBMM, так же популярны, как и те, у которых нет.
источник
Одним из главных преимуществ системы GC, о котором еще никто не упомянул, является то, что ссылка в системе GC гарантированно сохраняет свою идентичность до тех пор, пока она существует . Если кто-то вызывает
IDisposable.Dispose
(.NET) илиAutoCloseable.Close
(Java) объекта, в то время как копии ссылки существуют, эти копии будут продолжать ссылаться на тот же объект. Объект больше не будет полезен, но попытки использовать его будут иметь предсказуемое поведение, управляемое самим объектом. Напротив, в C ++, если код вызываетdelete
объект, а затем пытается его использовать, все состояние системы становится полностью неопределенным.Еще одна важная вещь, которую стоит отметить, это то, что управление памятью на основе области очень хорошо работает для объектов с четко определенным владельцем. Он работает гораздо менее хорошо, а иногда и совершенно плохо, с объектами, которые не имеют определенного владельца. Как правило, изменяемые объекты должны иметь владельцев, в то время как неизменяемые объекты не нужны, но есть небольшая проблема: в коде очень часто используется экземпляр изменяемых типов для хранения неизменяемых данных, гарантируя, что ни одна ссылка не будет открыта для доступа. код, который может изменить экземпляр. В таком сценарии экземпляры изменяемого класса могут совместно использоваться несколькими неизменяемыми объектами и, таким образом, не имеют явного владельца.
источник
Во-первых, очень важно понимать, что приравнивание RAII к SBMM. или даже в СРРМ. Одним из наиболее важных (и наименее известных или наиболее недооцененных) качеств RAII является тот факт, что он делает «ресурс» свойством, которое НЕ транзитивно для композиции.
Следующая запись блога обсуждает этот важный аспект RAII и противопоставляет его управлению ресурсами в языках GCed, которые используют недетерминированный GC.
http://minorfs.wordpress.com/2011/04/29/why-garbage-collection-is-anti-productive/
Важно отметить, что хотя RAII в основном используется в C ++, Python (наконец-то версия, не основанная на ВМ) имеет деструкторы и детерминированный GC, который позволяет использовать RAII вместе с GC. Лучший из обоих миров, если бы это было.
источник
File.ReadLines file |> Seq.length
где абстракции обрабатывают закрытие для меня. Замки и темы я заменил на .NETTask
и F #MailboxProcessor
. Вся эта «Мы взорвали объем ручного управления ресурсами» - просто полная чушь.