that is the question :)
Мне часто приходилось встречаться с таким мнением, мол в С++ нет сборки мусора, но она и не нужна, так-как есть умные указатели, с помощью которых можно так-же легко управлять памятью используя подсчет ссылок. Так вот, я думаю, что все это ерунда и попробую сейчас объяснить почему, в качестве примера умного указателя считающего ссылки, буду использовать boost::shared_ptr, под сборщиком мусора, я собираюсь понимать сборщик мусора CLR. :)
В том случае, если временем жизни объекта управляет сборщик мусора, указатели на объект, это просто указатели. В случае, если мы(а точнее приложение, которое мы пишем:) используем подсчет ссылок, то указатель на объект, это два указателя, указатель на сам объект, плюс указатель на переменную, содержащую количество ссылок на этот объект(не правда ли это прекрасно:).
Операции над указателями: в случае GC, мы просто копируем их, в случае not GC, копирование указателя приводит к изменению счетчика ссылок объекта. Копирование, передача в качестве параметра в функцию, удаление умного указателя, приводят к генерации барьера и изменению счетчика ссылок. К тому-же, сам указатель и его счетчик ссылок, могут находится далеко друг от друга в памяти, а это означает плохую локальность.
Память, в управляемой сборщиком мусора куче, выделяется быстро, по сути, выделение памяти, это смещение указателя, отделяющего свободную память от занятой, на размер объекта, плюс, некоторые сервисные операции, о которых я ничего не знаю :D. Время, затрачиваемое на выделение участка памяти в неуправляемой куче, зависит от размера этого участка, а так-же от фрагментации памяти, и оно достаточно велико.
Освобождение памяти: GC делает это достаточно долго, он должен найти недостижимые участки памяти, освободить их а затем дефрагментировать кучу. Но этот процесс достаточно хорошо оптимизирован, современные GC собирают мусор не везде, а только там, где он мог появиться.
Итак, подведу итог. Если использовать в качестве критерия сложность, то получается следующее: сам по себе, алгоритм подсчета ссылок очень прост, алгоритм работы современного сборщика мусора значительно сложнее. Но с точки зрения разработчика все наоборот. Сложности, связанные со сборкой мусора, берет на себя рантайм, а с подсчетом ссылок должен управляться сам программист. Он должен следить за тем, что-бы не возникали циклические ссылки, что-бы у одного объекта всегда был только один счетчик ссылок и так далее. В случае использования GC, нужно просто стараться не создавать для него лишнюю работу. Ну и самое главное – архитектура становится проще.
В плане производительности, делайте выводы сами, лично мне они кажутся достаточно очевидными. С днем всех святых! :)
14 комментариев:
насколько я помню, сборщик мусора CLR отнюдь не идеален, и при использовании ФЯ (и выделении очень большого кол-ва маленьких объектов), с ним возникает много проблем и проседаний в производительности - это кто-то из разработчиков F# писал
У меня недостаточно опыта, для того, что-бы судить, насколько сборщик мусора CLR хорош для ФЯ. Я недавно читал одну статью в MSDN. Там говорится о том, что нужно минимизировать количество изменений объектов, особенно тех, которые живут долго. Значит, неизменяемость объектов в ФЯ, хорошо скажется на производительности сборщика мусора CLR.
ну судя по статье, CLR реализует стандартную схему с generational gc, но видимо были какие-то особенности реализации, которые просаживали производительность для маленьких объектов - я если найду сслыку на то обсуждение, то кину в комментарий
Операции над указателями: в случае GC, мы просто копируем их, в случае not GC, копирование указателя приводит к изменению счетчика ссылок объекта.
А разве для того, чтобы работал GC у каждого объекта не присутвует счетчик ссылок, который точно так же увеличивается самим GC, и доступ к которому нужно точно так же разграничивать?
А разве для того, чтобы работал GC у каждого объекта не присутвует счетчик ссылок, который точно так же увеличивается самим GC, и доступ к которому нужно точно так же разграничивать?
Насколько я понимаю, в .NET, это просто указатели. Сборщик мусора строит граф, начиная с корневых объектов(статические объекты, объекты в стеке, и тд), для того, что-бы определить, содержит-ли тот объект указатели на другие объекты используется информация о типах, она там доступна динамически, а не только во время компиляции. Затем, алгоритм GC, с помощью обычного memcpy уплотняет кучу, сдвигая используемые объекты и обновляя указатели на них.
Если я правильно понял, то с копированием объектов этот граф содержащий root будет разрастаться. Скопировали один объект, добавился еще один root на него или не так?
Скажите, вы видели хоть одну программу упершуюся в скорость shared_ptr?
Ну и какой смысл руссуждать о скорости того, что занимает 0% времени CPU? :-)
Что же касается скорости, заметьте:
1 Не все объекты создаются в хипе.
2 Не под каждый объект создаётся свой блок памяти.
3 Не на все объекты, под которые создается свой блок памяти, ссылаются используя shared_ptr.
И, кстати...
4 сборщик мусора, тоже, не бесплатная вещь.
Скажите, вы видели хоть одну программу упершуюся в скорость shared_ptr?Я видел программы упершиеся в скорость аллокации, а так-же огромное количество ошибок работы с памятью. И я не сомневаюсь, что можно написать такую программу, которая таки упрется в скорость shared_ptr :D
> упершиеся в скорость аллокации
Вот именно в скорость аллокации, а не скорость shared_ptr.
Давайте всё-таки различать аллокацию памяти и подсчет ссылок.
Apple, начиная с набора инструментов разработчика для Mac OS X 10.6, настаивает на использование GC в Cocoa-проектах.
Пруфпик с презенташки одной из сессий WWDC '09
Apple, начиная с набора инструментов разработчика для Mac OS X 10.6, настаивает на использование GC в Cocoa-проектах.
Пруфпик с презенташки одной из сессий WWDC '09
ну судя по статье, CLR реализует стандартную схему с generational gc, но видимо были какие-то особенности реализации, которые просаживали производительность для маленьких объектов - я если найду сслыку на то обсуждение, то кину в комментарий
Если я правильно понял, то с копированием объектов этот граф содержащий root будет разрастаться. Скопировали один объект, добавился еще один root на него или не так?
Отправить комментарий