Подтвердить что ты не робот

RAII против сборщика мусора

Недавно я смотрел замечательный разговор Херба Саттера о "Leak Free С++..." на CppCon 2016, где он рассказывал об использовании интеллектуальных указателей для реализации RAII (инициализация ресурсов - инициализация) - концепции и то, как они решают большую часть памяти проблемы утечки.

Теперь мне было интересно. Если я строго следую правилам RAII, что, кажется, хорошо, почему это может отличаться от того, чтобы сборщик мусора на С++? Я знаю, что с RAII программист полностью контролирует, когда ресурсы снова освобождаются, но разве это полезно для того, чтобы иметь сборщик мусора? Будет ли это действительно менее эффективным? Я даже слышал, что сборщик мусора может быть более эффективным, поскольку он может освобождать более крупные куски памяти за раз, а не освобождать небольшие фрагменты памяти по всему коду.

4b9b3361

Ответ 1

Если я строго соблюдаю правила RAII, что кажется хорошим, почему это может быть иначе, чем сборщик мусора на С++?

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

Вы можете реализовать GC, хотя для конкретных случаев, с очень разными характеристиками производительности. Я реализовал один раз для закрытия соединений сокетов, на высокопроизводительном/высокопроизводительном сервере (просто вызов API закрытия сокетов занял слишком много времени и укрепил производительность). Это не связано с памятью, но сетевыми соединениями и без циклической обработки зависимостей.

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

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

В таких случаях GC не вырезает его, что является причиной в С# (например), у вас есть интерфейс IDisposable.

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

Может быть... зависит от реализации.

Ответ 2

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

Это дает два преимущества. Во-первых, будут определенные типы проблем, которые RAII не может решить. Это, по моему опыту, редко.

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

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

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

Большинство реализаций GC также приводят к полному развертыванию нелокальных классов; создание смежных буферов общих объектов или составление общих объектов в одном более крупном объекте - это не то, что облегчает большинство реализации GC. С другой стороны, С# позволяет создать тип значения struct с несколько ограниченными возможностями. В нынешнюю эпоху архитектуры процессора ключевое значение имеет кеш-дружелюбие, а нехватка локальных ГК-сил - тяжелое бремя. Поскольку в большинстве случаев эти языки имеют время исполнения байт-кода, теоретически среда JIT может перемещать часто используемые данные вместе, но чаще всего вы просто получаете единообразную потерю производительности из-за частых промахов в кеше по сравнению с С++.

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

Ответ 3

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

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

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

С подсчетом ссылок вам нужно специально создать для них код (используя слабые ссылки или другие материалы).

Во многих полезных случаях (считайте std::vector<std::map<std::string,int>>) подсчет ссылок является неявным (поскольку он может быть только 0 или 1) и практически опускается, но функции конструктора и деструктора (необходимые для RAII) ведут себя так, как если бы они были опорный счетный бит (который практически отсутствует). В std::shared_ptr имеется подлинный счетчик ссылок. Но память по-прежнему неявно вручную управляется (при запуске new и delete внутри конструкторов и деструкторов), но этот "неявный" delete (в деструкторах) создает иллюзию автоматического управления памятью. Однако вызовы new и delete все еще происходят (и они стоят времени).

Кстати, реализация GC может (и часто) обрабатывать цикличность каким-то особым образом, но вы оставляете эту нагрузку для GC (например, читайте о алгоритме Чейни).

Некоторые алгоритмы GC (в частности, сборщик копировальных копировальных копировальных копий) не беспокоят освобождение памяти для отдельных объектов, это освобождение в массовом порядке после копии. На практике Ocaml GC (или SBCL один) может быть быстрее, чем подлинный стиль программирования С++ RAII (для некоторых, а не для всех, алгоритмов).

Некоторые GC предоставляют finalization (в основном используются для управления внешними ресурсами, отличными от памяти, например, файлы), но вы редко используете его ( поскольку большинство значений потребляют только ресурсы памяти). Недостатком является то, что окончательная доработка не предусматривает какой-либо гарантии времени. Практически говоря, программа, использующая финализацию, использует ее как последнее средство (например, закрытие файлов должно происходить более или менее явно за пределами финализации, а также с ними). ​​

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

Я рекомендую прочитать сборник мусора.

В вашем коде на С++ вы можете использовать Boehm GC или Ravenbrook MPS или введите свой собственный трассировочный сборщик мусора. Конечно, использование GC - это компромисс (есть некоторые неудобства, например, недетерминированность, отсутствие гарантий синхронизации и т.д.).

Я не думаю, что RAII - это лучший способ справиться с памятью во всех случаях. В некоторых случаях кодирование вашей программы в подлинно и эффективно реализациях GC (думаю, Ocaml или SBCL) может быть проще (для разработки) и быстрее (для выполнения), чем кодирование его с фантазийным стилем RAII в С++ 17. В других случаях это не так. YMMV.

В качестве примера, если вы кодируете интерпретатор Scheme в С++ 17 с самым причудливым стилем RAII, вам все равно нужно будет кодовать (или использовать) явный GC внутри него (потому что куча схемы имеет круговые значения). И большинство помощников по доказательству закодированы на языках GC-ed, часто функциональных (единственный, который я знаю, который закодирован на С++, Lean) по уважительным причинам.

Кстати, мне интересно найти такую ​​реализацию схемы С++ 17 (но менее заинтересована в ее кодировании), желательно с некоторой многопотоковой способностью.

Ответ 4

RAII и GC решают проблемы в совершенно разных направлениях. Они совершенно разные, несмотря на то, что некоторые скажут.

Оба относятся к проблеме, которая затрудняет управление ресурсами. Garbage Collection решает его, делая это так, чтобы разработчику не нужно уделять столько внимания управлению этими ресурсами. RAII решает его, упрощая разработчикам обращать внимание на управление ресурсами. Любой, кто говорит, что делает то же самое, может продать вас.

Если вы посмотрите на последние тенденции в языках, вы видите, что оба подхода используются на одном языке, потому что, честно говоря, вам действительно нужны обе стороны головоломки. Вы видите множество языков, которые используют сортировку мусора, поэтому вам не нужно обращать внимание на большинство объектов, и эти языки также предлагают RAII-решения (например, оператор python with) на время, которое вы действительно хотите обратите внимание на них.

  • С++ предлагает RAII через конструкторы/деструкторы и GC через shared_ptr (Если я могу сделать аргумент, что refcounting и GC находятся в одном классе решений, потому что они оба предназначены, чтобы помочь вам не нужно обращать внимание на продолжительность жизни )
  • Python предлагает RAII через with и GC через систему refcounting плюс сборщик мусора
  • С# предлагает RAII через IDisposable и using и GC через коллективный сборщик мусора

Образцы появляются на каждом языке.

Ответ 5

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

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

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

Ответ 6

Грубо говоря. Идиома RAII может быть лучше для латентности и дрожания. Сборщик мусора может быть лучше для пропускной способности системы.

Ответ 7

"Эффективный" - очень широкий термин, в смысле усилий по развитию RAII обычно менее эффективен, чем GC, но с точки зрения производительности GC обычно менее эффективен, чем RAII. Однако для обоих случаев можно предоставить contr-примеры. Работа с общим GC, когда у вас есть очень четкие патчи распределения ресурсов (де) на управляемых языках, может быть довольно неприятной, так же как код с использованием RAII может быть неожиданно неэффективен, когда shared_ptr используется для всего без причины.

Ответ 8

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

Помимо этого, вы можете в основном почувствовать напряжение древнего "Ява или C++ - лучший язык"? flamewar треск в комментариях. Интересно, какой "приемлемый" ответ на этот вопрос мог бы выглядеть, и мне любопытно увидеть его в конце концов.

Но еще одно замечание о возможно важном различии концептуальных еще не указано: с RAII вы привязаны к потоку, который вызывает деструктор. Если ваше приложение однопоточное (и хотя Herb Sutter заявил, что The Free Lunch Is Over: большинство программ сегодня эффективно, threaded), то одно ядро ​​может быть занято обработкой очистки объектов, которые больше не актуальны для реальной программы...

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

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

Ответ 9

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

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

Ответ 10

Сбор мусора и RAII поддерживают одну общую конструкцию, для которой другая не подходит.

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

С другой стороны, RAII отлично справляется с ситуациями, когда объект должен приобретать эксклюзивные сервисы от внешних объектов. Хотя многие системы ГК позволяют объектам определять методы "Завершить" и запрашивать уведомление, когда их обнаруживают, что они оставлены, и таким методам иногда удается освободить внешние службы, которые больше не нужны, они редко являются достаточно надежными, чтобы обеспечить удовлетворительный способ обеспечение своевременного выпуска внешних услуг. Для управления неисправимыми внешними ресурсами RAII ударяет GC руками.

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

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

Ответ 11

RAII и сбор мусора предназначены для решения различных проблем.

Когда вы используете RAII, вы оставляете объект в стеке, единственной целью которого является очистка того, что вы хотите управлять (сокеты, память, файлы и т.д.) при выходе из области действия метода. Это для исключение-безопасность, а не просто сборка мусора, поэтому вы получаете ответы о закрытии сокетов и освобождении мьютексов и т.п. (Хорошо, поэтому никто не упомянул о мьютексах, кроме меня.) Если исключение выбрано, то удаление стека естественно очищает ресурсы, используемые методом.

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

Сбор мусора также относится к объектам, которые распределены по кустам, когда, например, factory создает экземпляр объекта и возвращает его. Наличие постоянных объектов в ситуациях, когда контроль должен оставить область действия, делает сборку мусора привлекательной. Но вы можете использовать RAII в factory, поэтому, если перед возвратом выдается исключение, вы не утечка ресурсов.