Возможные подводные камни этого асинхронного сборщика мусора

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

Вот как я об этом думал:

  • каждый поток имеет собственное пространство кучи для управления и хранит список принадлежащих ему указателей, которые используются другими потоками. При этом сборка мусора работает полностью асинхронно с запущенными потоками, и:
  • Фаза 1 начинается с отслеживания корней потоков и маркировки всех достижимых ими объектов. Если мы попадаем в пространство другого потока, мы прекращаем следовать этому указателю и помечаем этот указатель как «используемый» в потоке-владельце.
  • после того, как мы пометили все регионы, мы выбираем регион (возможно, с наибольшим количеством мертвых ссылок) и начинаем копировать его живые ссылки на объекты в другое пространство. (это может быть все пространство кучи потоков, но я думаю, что эта операция может быть слишком интенсивной по памяти)
  • копирование начинается с установки с помощью CAS флага, указывающего на то, что объект копируется. Любое изменяемое действие, выполняемое над этим конкретным объектом, пока этот флаг установлен, будет вращать блокировку до тех пор, пока новый адрес не будет установлен потоком gc. Когда копирование заканчивается, новый адрес устанавливается на старый, и любая изменяемая ссылка, которая должна быть выполнена на объекте, будет перенаправляться на новый объект.
  • после обновления всех ссылок, сделанных на эти указатели с помощью CAS, старое пространство, наконец, освобождается (новые указатели не будут обновлены с неправильным адресом, поскольку каждый мутатор сначала проверяет, изменились ли ссылки)

Вот и все!

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

Я совсем не уверен в том, как, например. он будет обрабатывать циклические ссылки из разных потоков. Я думаю, что это будет обработано естественным образом, поскольку мы обновляем все указатели опасностей, которые есть в текущем потоке gc'ed. Также может быть какой-то одновременный доступ, который я не рассматривал.

Благодарю вас!

---- РЕДАКТИРОВАТЬ: благодаря вкладу Эрнеста я думаю не об использовании алгоритма копирования, а, возможно, о простой маркировке и развертке. Это потому, что нам нужно будет проверять каждый раз, когда мы обращаемся к переменной объекта, был ли обновлен указатель. Мне кажется, это довольно большие накладные расходы. Не так ли?


person Waneck    schedule 16.04.2011    source источник
comment
Какой вопрос? К чему это относится? .NET или что-то еще?   -  person PiZzL3    schedule 16.04.2011
comment
Нет, извините, если я выразился не очень ясно (или не очень точно). В более общем смысле это относится к алгоритму сборки мусора, который может быть реализован для работы в виртуальной машине или что-то в этом роде. Вопрос: какие у него возможные недостатки?   -  person Waneck    schedule 16.04.2011
comment
в моем случае я думаю о реализации компилятора с языка в код C, и для этого потребуется сборка мусора!   -  person Waneck    schedule 16.04.2011
comment
Waneck, вам понадобится какая-то форма программного барьера для чтения.   -  person bestsss    schedule 17.04.2011
comment
Спасибо. Вы правы, мне действительно нужен барьер для чтения. Это действительно самая большая проблема. Спасибо!   -  person Waneck    schedule 17.04.2011
comment
@Waneck, что касается накладных расходов, нет, они не такие большие. Вы можете посмотреть исходный документ/черновик GC без пауз. org/events/vee05/full_papers/p46-click.pdf теперь есть реальная реализация, барьер чтения — несколько инструкций, но они действительно нужны вам перед каждой загрузкой. Вы можете упростить барьер, как предложил Дэйв Мун azulsystems.com/blog/cliff/ Кроме того, сам блог является выдающимся источником идей...   -  person bestsss    schedule 18.04.2011


Ответы (3)


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

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

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

Фаза 1 начинается с отслеживания корней потоков и маркировки всех достижимых ими объектов. Если мы попадаем в пространство другого потока, мы прекращаем следовать этому указателю и помечаем этот указатель как «используемый» в потоке-владельце.

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

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

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

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

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

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

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

Ok.

person J D    schedule 19.06.2012
comment
Спасибо за ваши очень ценные идеи! Я спрашивал об этом некоторое время назад, и, глядя сейчас, я действительно вижу, что этот способ справиться с ними имеет много проблем, особенно с параллельной маркировкой (на самом деле это не сработает). Но отвечая на ваши вопросы: - Я думаю, что право собственности на объект сделать довольно просто, поток, создавший объект, будет им владеть. Если объект является общим, это означает, что он будет отмечен в этом внешнем списке ссылок, что я все еще считаю хорошей идеей;) - person Waneck; 24.06.2012
comment
- Блокировка вращения будет достаточно быстрой (достаточно времени, чтобы скопировать объект), чтобы не быть запретительной, я думаю. Конечно, следует проявлять особую осторожность для больших объектов, но в любом случае это всегда так для копирования gc :) - person Waneck; 24.06.2012
comment
Почему копирование насыщает пропускную способность глобальной памяти? - person Waneck; 24.06.2012

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

person Ernest Friedman-Hill    schedule 16.04.2011
comment
Вы имеете в виду, что после копирования объекта все старые ссылки по-прежнему будут видеть устаревшую версию? Будет ли проверка того, был ли обновлен указатель объекта, ударом по производительности? - person Waneck; 16.04.2011

Недавно видел это http://java-monitor.com/forum/showthread.php?t=890

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

person c-smile    schedule 16.04.2011
comment
Благодарю вас! Я не знал, как работает gc в erlang, но, думаю, я ищу почти такие же требования. Проблема в том, что erlang может достичь этого без особых проблем и спин-блокировок, потому что все неизменяемо, поэтому не стоит беспокоиться о том, что у вас нет самой последней версии / или об изменении местоположения указателей. Мне все равно придется иметь с ними дело; ) - person Waneck; 16.04.2011
comment
Ванеку: Я лучше сосредоточусь на ускорении работы GC. Сборщики мусора Stop-the-world просты и предсказуемы. В принципе инкрементальный сборщик мусора — это попытка двигаться в этом направлении. - person c-smile; 16.04.2011
comment
И все же... Еще один вектор решения проблем с кучей - сделать язык/VM сам по себе менее жадным до памяти. Например, в моем TIScript есть функция, называемая множественными возвратами. Скажем, вам нужно вернуть прямоугольник (x, y, w, h) из функции. В Java вы сделаете это как: Rectangle r = wdg.getRect(); // does new Rectangle()..., но более эффективно сделать это как: var (x,y,w,h) = wdg.getRect(); // returns four values on stack И GC не потребуется. - person c-smile; 16.04.2011
comment
да, в этом вы правы, они безопаснее. Однако я ищу две области, в которых было бы полезно иметь не приостанавливающий gc. Конечно, есть и другие выходы, и я мог бы даже пойти в другом направлении позже. Но пока я просто не могу найти вескую причину, по которой такой алгоритм был бы плохим. И истинная сила этого, я думаю, заключается в том, что приложение никогда не будет останавливаться. - person Waneck; 16.04.2011
comment
Однако я думал о копировании gc, и, похоже, это приносит гораздо больше хлопот, чем того стоит. Может быть, что-то вроде Immix (portal.acm.org/citation.cfm?id= 1375581.1375586 ) было бы лучше, особенно чтобы избежать путаницы с изменением указателя (на которую указал Эрнест!) - person Waneck; 16.04.2011