воскресенье, 1 ноября 2009 г.

GC or not GC

that is the question :)

Мне часто приходилось встречаться с таким мнением, мол в С++ нет сборки мусора, но она и не нужна, так-как есть умные указатели, с помощью которых можно так-же легко управлять памятью используя подсчет ссылок. Так вот, я думаю, что все это ерунда и попробую сейчас объяснить почему, в качестве примера умного указателя считающего ссылки, буду использовать boost::shared_ptr, под сборщиком мусора, я собираюсь понимать сборщик мусора CLR. :)

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

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

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

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

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

В плане производительности, делайте выводы сами, лично мне они кажутся достаточно очевидными. С днем всех святых! :)

14 комментариев:

Alex Ott комментирует...

насколько я помню, сборщик мусора CLR отнюдь не идеален, и при использовании ФЯ (и выделении очень большого кол-ва маленьких объектов), с ним возникает много проблем и проседаний в производительности - это кто-то из разработчиков F# писал

Lazin комментирует...

У меня недостаточно опыта, для того, что-бы судить, насколько сборщик мусора CLR хорош для ФЯ. Я недавно читал одну статью в MSDN. Там говорится о том, что нужно минимизировать количество изменений объектов, особенно тех, которые живут долго. Значит, неизменяемость объектов в ФЯ, хорошо скажется на производительности сборщика мусора CLR.

Alex Ott комментирует...

ну судя по статье, CLR реализует стандартную схему с generational gc, но видимо были какие-то особенности реализации, которые просаживали производительность для маленьких объектов - я если найду сслыку на то обсуждение, то кину в комментарий

way комментирует...

Операции над указателями: в случае GC, мы просто копируем их, в случае not GC, копирование указателя приводит к изменению счетчика ссылок объекта.

А разве для того, чтобы работал GC у каждого объекта не присутвует счетчик ссылок, который точно так же увеличивается самим GC, и доступ к которому нужно точно так же разграничивать?

Lazin комментирует...

А разве для того, чтобы работал GC у каждого объекта не присутвует счетчик ссылок, который точно так же увеличивается самим GC, и доступ к которому нужно точно так же разграничивать?
Насколько я понимаю, в .NET, это просто указатели. Сборщик мусора строит граф, начиная с корневых объектов(статические объекты, объекты в стеке, и тд), для того, что-бы определить, содержит-ли тот объект указатели на другие объекты используется информация о типах, она там доступна динамически, а не только во время компиляции. Затем, алгоритм GC, с помощью обычного memcpy уплотняет кучу, сдвигая используемые объекты и обновляя указатели на них.

way комментирует...

Если я правильно понял, то с копированием объектов этот граф содержащий root будет разрастаться. Скопировали один объект, добавился еще один root на него или не так?

Ivan Sorokin комментирует...

Скажите, вы видели хоть одну программу упершуюся в скорость shared_ptr?

Ну и какой смысл руссуждать о скорости того, что занимает 0% времени CPU? :-)

Что же касается скорости, заметьте:

1 Не все объекты создаются в хипе.
2 Не под каждый объект создаётся свой блок памяти.
3 Не на все объекты, под которые создается свой блок памяти, ссылаются используя shared_ptr.

И, кстати...
4 сборщик мусора, тоже, не бесплатная вещь.

Lazin комментирует...

Скажите, вы видели хоть одну программу упершуюся в скорость shared_ptr?Я видел программы упершиеся в скорость аллокации, а так-же огромное количество ошибок работы с памятью. И я не сомневаюсь, что можно написать такую программу, которая таки упрется в скорость shared_ptr :D

Ivan Sorokin комментирует...

> упершиеся в скорость аллокации

Вот именно в скорость аллокации, а не скорость shared_ptr.

Давайте всё-таки различать аллокацию памяти и подсчет ссылок.

kemiisto комментирует...

Apple, начиная с набора инструментов разработчика для Mac OS X 10.6, настаивает на использование GC в Cocoa-проектах.

Пруфпик с презенташки одной из сессий WWDC '09

Bobi комментирует...
Этот комментарий был удален администратором блога.
kemiisto комментирует...

Apple, начиная с набора инструментов разработчика для Mac OS X 10.6, настаивает на использование GC в Cocoa-проектах.

Пруфпик с презенташки одной из сессий WWDC '09

Alex Ott комментирует...

ну судя по статье, CLR реализует стандартную схему с generational gc, но видимо были какие-то особенности реализации, которые просаживали производительность для маленьких объектов - я если найду сслыку на то обсуждение, то кину в комментарий

way комментирует...

Если я правильно понял, то с копированием объектов этот граф содержащий root будет разрастаться. Скопировали один объект, добавился еще один root на него или не так?