G
logo
Glow labs
Создание распределенной ОС для людей
G
logo
Glow labs

Внутри сборщика мусора D


На ‎конференции‏ ‎DConf ‎2017 ‎в ‎хакатоне ‎я‏ ‎вел ‎группу‏ ‎(из‏ ‎2 ‎человек) ‎занимающуюся‏ ‎druntime ‎—‏ ‎ковырявшуюся ‎в ‎сборщике ‎мусора‏ ‎D.‏ ‎Спустя ‎пару‏ ‎часов ‎я‏ ‎не ‎мог ‎стряхнуть ‎чувство ‎—‏ ‎черт‏ ‎это ‎все‏ ‎неплохо ‎бы‏ ‎переписать. ‎Так ‎что ‎я ‎решил‏ ‎начать‏ ‎задачу‏ ‎построения ‎лучшего‏ ‎сборщика ‎мусора‏ ‎для ‎D.‏ ‎Первая‏ ‎итерация ‎должна‏ ‎быть ‎более ‎быстрым ‎классическим ‎mark-sweep‏ ‎сборщиком.

Чтобы ‎объяснить‏ ‎мою‏ ‎мотивацию ‎я ‎покажу‏ ‎внутренности ‎текущего‏ ‎сборщика ‎мусора, ‎перечислю ‎все‏ ‎проблемы‏ ‎его ‎дизайна.‏ ‎Так ‎или‏ ‎иначе, ‎мы ‎должны ‎анализировать ‎то,‏ ‎что‏ ‎мы ‎имеем,‏ ‎чтобы ‎понять‏ ‎куда ‎идти ‎дальше.

Пулы, ‎пулы ‎повсюду

Если‏ ‎бы‏ ‎мы‏ ‎проигнорировали ‎некоторую‏ ‎глобальную ‎машинерию‏ ‎то ‎сборщик‏ ‎мусора‏ ‎это ‎просто‏ ‎массив ‎пулов. ‎Каждый ‎пул ‎это‏ ‎кусок ‎mmap-нутой‏ ‎памяти‏ ‎+ ‎пачка ‎malloc-нутых‏ ‎метаданных ‎таких‏ ‎как ‎таблицы ‎для ‎mark‏ ‎битов,‏ ‎free ‎битов‏ ‎и ‎так‏ ‎далее. ‎Все ‎аллокации ‎происходят ‎внутри‏ ‎пула,‏ ‎если ‎ни‏ ‎один ‎пул‏ ‎не ‎может ‎аллоцировать ‎память, ‎то‏ ‎создается‏ ‎новый‏ ‎пул. ‎Размер‏ ‎пула ‎определяется‏ ‎арифметической ‎прогрессией‏ ‎по‏ ‎числу ‎пулов‏ ‎или ‎150% ‎размера ‎аллокации, ‎в‏ ‎зависимости ‎от‏ ‎того,‏ ‎что ‎больше.

Любая ‎маленькая‏ ‎аллокация ‎сперва‏ ‎округляется ‎до ‎одной ‎из‏ ‎степеней‏ ‎2 ‎(класс‏ ‎размера) ‎—‏ ‎16, ‎32, ‎64, ‎128,256, ‎512,‏ ‎1024,‏ ‎2048.Затем ‎проверяется‏ ‎глобальный ‎freelist‏ ‎для ‎этих ‎классов ‎размеров, ‎если‏ ‎не‏ ‎удается‏ ‎найти ‎мы‏ ‎ищем ‎маленький‏ ‎пул. ‎Этот‏ ‎маленький‏ ‎пул ‎будет‏ ‎аллоцировать ‎новую ‎страницу ‎связывать ‎ее‏ ‎с ‎free‏ ‎list‏ ‎объектов ‎это ‎класса‏ ‎размеров. ‎И‏ ‎тут ‎видна ‎первая ‎ошибка‏ ‎дизайна‏ ‎— ‎класс‏ ‎размера ‎назначается‏ ‎каждой ‎странице, ‎и ‎следовательно ‎нам‏ ‎нужна‏ ‎таблица, ‎которая‏ ‎отражает ‎страницу‏ ‎пула ‎в ‎класс ‎размер ‎(называемой‏ ‎pagetable).

Теперь‏ ‎чтобы‏ ‎найти ‎начало‏ ‎объекта ‎из‏ ‎внутреннего ‎указателя‏ ‎мы‏ ‎сперва ‎обнаруживаем‏ ‎страницу ‎к ‎которой ‎он ‎относится,‏ ‎затем ‎находим‏ ‎ее‏ ‎класс ‎и ‎наконец‏ ‎маскируем ‎биты‏ ‎чтобы ‎получить ‎начало ‎объекта.‏ ‎Более‏ ‎того ‎метаданные‏ ‎это ‎просто‏ ‎пачка ‎простых ‎побитовых ‎таблиц, ‎которым‏ ‎нужно‏ ‎подстроится ‎под‏ ‎гетерогенные ‎страницы,‏ ‎что ‎делается ‎путем ‎выделения ‎~7‏ ‎бит‏ ‎на‏ ‎16 ‎байт‏ ‎вне ‎зависимости‏ ‎от ‎размера‏ ‎объекта.

Что‏ ‎смотивировало ‎такой‏ ‎дизайн? ‎У ‎меня ‎две ‎гипотезы.‏ ‎Первая ‎это‏ ‎нежелание‏ ‎резервировать ‎память ‎для‏ ‎недостаточно ‎утилизированных‏ ‎пулов, ‎что ‎не ‎является‏ ‎проблемой‏ ‎из-за ‎того‏ ‎как ‎устроена‏ ‎виртуальная ‎память ‎с ‎ленивым ‎выделением‏ ‎ресурсов.‏ ‎Вторая ‎это‏ ‎опасения ‎выделения‏ ‎слишком ‎большого ‎числа ‎пулов, ‎замедляя‏ ‎выделение‏ ‎памяти‏ ‎и ‎что‏ ‎любопытно ‎фазу‏ ‎mark ‎сборщика‏ ‎мусора.‏ ‎Последнее ‎скорее‏ ‎всего ‎и ‎есть ‎причина, ‎поскольку‏ ‎сборщик ‎мусора‏ ‎делает‏ ‎линейный ‎проход ‎по‏ ‎пулам ‎довольно‏ ‎часто ‎и ‎двоичный ‎поиск‏ ‎для‏ ‎каждого ‎потенциального‏ ‎указателя ‎во‏ ‎время ‎фазы ‎mark.

Это ‎подводит ‎нас‏ ‎ко‏ ‎второй ‎ошибке‏ ‎поиск ‎пула‏ ‎за ‎logP, ‎где ‎P ‎число‏ ‎пулов,‏ ‎что‏ ‎делает ‎mark‏ ‎NlogP ‎задачей.‏ ‎Хештаблица ‎могла‏ ‎бы‏ ‎сэкономить ‎изрядное‏ ‎число ‎циклов.

Завершая ‎наш ‎обзор ‎малых‏ ‎пулов ‎мы‏ ‎также‏ ‎рассмотрим ‎выбор ‎классов‏ ‎размера. ‎Это‏ ‎третья ‎проблема ‎(не ‎ошибка,‏ ‎но‏ ‎спорное ‎решение)‏ ‎в ‎выборе‏ ‎степеней ‎двойки, ‎что ‎гарантирует ‎нам‏ ‎вплоть‏ ‎до ‎50%‏ ‎внутренней ‎фрагментации.‏ ‎Современные ‎аллокаторы ‎такие ‎как ‎jemalloc‏ ‎предоставляют‏ ‎по‏ ‎одному ‎классы‏ ‎между ‎степенями‏ ‎двоек. ‎Деление‏ ‎на‏ ‎константу ‎не‏ ‎степени ‎двойки ‎немного ‎дороже ‎чем‏ ‎один ‎AND,‏ ‎но‏ ‎все ‎же ‎вполне‏ ‎приемлимы ‎по‏ ‎скорости.

Давайте ‎посмотрим ‎на ‎пулы‏ ‎для‏ ‎больших ‎объектов.‏ ‎Первое, ‎что‏ ‎бросается ‎в ‎глаза ‎это ‎то,‏ ‎что‏ ‎гранулярность ‎одна‏ ‎страница ‎памяти‏ ‎— ‎4кб ‎для ‎метаданных ‎и‏ ‎аллокаций.‏ ‎Ряды‏ ‎свободных ‎страниц‏ ‎связаны ‎в‏ ‎free ‎list,‏ ‎который‏ ‎сканируется ‎линейно‏ ‎для ‎каждой ‎аллокации. ‎Это ‎четвертая‏ ‎ошибка, ‎то‏ ‎есть‏ ‎с ‎производительностью ‎больших‏ ‎аллокаций ‎решили‏ ‎не ‎заморачиваться. ‎Чтобы ‎обнаружить‏ ‎начало‏ ‎объекта ‎поддерживается‏ ‎отдельная ‎таблица,‏ ‎где ‎для ‎каждой ‎страницы ‎хранится‏ ‎индекс‏ ‎начала ‎относящегося‏ ‎к ‎ней‏ ‎объекта. ‎Эта ‎схема ‎кажется ‎разумной‏ ‎пока‏ ‎мы‏ ‎не ‎задумаемся‏ ‎над ‎большими‏ ‎аллокациями ‎100+‏ ‎Mb,‏ ‎поскольку ‎почти‏ ‎наверняка ‎не ‎сможет ‎релоцироваться ‎по‏ ‎месту ‎и‏ ‎создаст‏ ‎новый ‎пул, ‎что‏ ‎израсходует ‎впустую‏ ‎огромный ‎объем ‎памяти ‎на‏ ‎мета‏ ‎данные ‎того,‏ ‎что ‎по‏ ‎сути ‎является ‎одним ‎объектом.

Сборка

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

На‏ ‎верхнем ‎уровне‏ ‎сборка ‎неожиданно ‎занимает ‎аж ‎4‏ ‎фазы: ‎prepare,‏ ‎markAll,‏ ‎sweep ‎и ‎recover.

Стадия‏ ‎prepare ‎самая‏ ‎странная, ‎поскольку ‎по ‎сути‏ ‎она‏ ‎должна ‎быть‏ ‎просто ‎«скопировать‏ ‎free ‎биты ‎в ‎mark ‎биты»‏ ‎(чтобы‏ ‎избежать ‎сканирования‏ ‎свободной ‎памяти).‏ ‎Ситуация ‎осложнена ‎тем ‎что ‎мы‏ ‎вычисляем‏ ‎свободное‏ ‎простоанство ‎путем‏ ‎обхода ‎всех‏ ‎free ‎list.‏ ‎Это‏ ‎5-я ‎(?)‏ ‎ошибка ‎— ‎скакание ‎дополнительно ‎по‏ ‎неизвестному ‎числу‏ ‎указателей‏ ‎это ‎последнее ‎что‏ ‎стоит ‎делать‏ ‎во ‎время ‎stop ‎the‏ ‎world‏ ‎паузы. ‎Более‏ ‎удачный ‎дизайн‏ ‎это ‎переключать ‎биты ‎при ‎аллокации,‏ ‎особенно‏ ‎с ‎учетом‏ ‎того, ‎что‏ ‎free ‎list ‎поддерживает ‎указатели ‎на‏ ‎пулы‏ ‎для‏ ‎каждого ‎объекта‏ ‎чтобы ‎не‏ ‎выполнять ‎поиск‏ ‎пула.

Реальный‏ ‎mark ‎выполняется‏ ‎в ‎вызове ‎markAll, ‎которая ‎просто‏ ‎передает ‎подходящие‏ ‎диапазоны‏ ‎памяти ‎в ‎функцию‏ ‎mark. ‎Эта‏ ‎функция ‎заслуживает ‎более ‎глубокого‏ ‎рассмотрения.

  1. Для‏ ‎каждого ‎указателя‏ ‎в ‎диапазоне‏ ‎памяти ‎она ‎сперва ‎проверяет ‎попадает‏ ‎ли‏ ‎адрес ‎в‏ ‎диапазон ‎адресов‏ ‎кучи ‎сборщика ‎мусора ‎(от ‎наименьшего‏ ‎адреса‏ ‎в‏ ‎пуле ‎до‏ ‎наибольшего ‎адреса‏ ‎в ‎пуле).
  2. После‏ ‎этого‏ ‎идет ‎пугающий‏ ‎двоичный ‎поиск, ‎чтобы ‎найти ‎соответствующий‏ ‎пул ‎для‏ ‎указателя.
  3. Независимо‏ ‎от ‎типа ‎пула‏ ‎делается ‎чтение‏ ‎из ‎таблицы ‎страниц ‎текущего‏ ‎пула‏ ‎чтобы ‎посмотреть‏ ‎класс ‎размера‏ ‎или ‎является ‎ли ‎это ‎большим‏ ‎объектом,‏ ‎или ‎даже‏ ‎свободной ‎памятью.‏ ‎Небольшая ‎оптимизация ‎состоит ‎в ‎том,‏ ‎что‏ ‎это‏ ‎чтение ‎также‏ ‎говорит ‎нам,‏ ‎является ‎ли‏ ‎это‏ ‎началом ‎большого‏ ‎объекта ‎или ‎его ‎продолжением. ‎У‏ ‎нас ‎3‏ ‎случая‏ ‎— ‎маленький ‎объект,‏ ‎большой ‎объект‏ ‎или ‎продолжение ‎большого ‎объекта.‏ ‎Последние‏ ‎два ‎случая‏ ‎одинаковы ‎за‏ ‎исключением ‎дополнительного ‎чтения ‎из ‎таблицы.
  4. Определение‏ ‎начала‏ ‎объекта ‎маскирование‏ ‎бит ‎с‏ ‎соответствующей ‎маской, ‎плюс ‎в ‎случае‏ ‎продолжения‏ ‎большого‏ ‎объекта ‎дополнительное‏ ‎чтение ‎из‏ ‎таблицы ‎смещений.
  5. В‏ ‎случае‏ ‎большого ‎объекта‏ ‎существует ‎бит ‎«нет ‎внутренних ‎указателей»,‏ ‎что ‎позволяет‏ ‎игнорировать‏ ‎указатели ‎внутрь ‎объекта.
  6. Наконец‏ ‎проверить ‎и‏ ‎выставить ‎бит ‎mark, ‎если‏ ‎он‏ ‎не ‎был‏ ‎выставлен ‎или‏ ‎стоит ‎noscan ‎бит ‎добавить ‎объект‏ ‎к‏ ‎стеку ‎памяти‏ ‎под ‎сканирование.

Пропуская‏ ‎любопытные ‎манипуляции ‎со ‎стеком ‎(чтобы‏ ‎избежать‏ ‎переполнения‏ ‎стека ‎и‏ ‎в ‎то‏ ‎же ‎время‏ ‎использовать‏ ‎стековую ‎аллокацию)‏ ‎это ‎все, ‎что ‎у ‎нас‏ ‎есть ‎в‏ ‎mark‏ ‎функции. ‎Кроме ‎уже‏ ‎упоминавшегося ‎поиска‏ ‎пулов, ‎неэффективностей ‎полно. ‎Смещивание‏ ‎памяти‏ ‎без ‎указателей‏ ‎с ‎обычной‏ ‎памятью ‎в ‎одном ‎пуле ‎дает‏ ‎нам‏ ‎дополнительное ‎чтение‏ ‎битовой ‎таблицы‏ ‎на ‎критическом ‎пути. ‎Аналогично ‎чтение‏ ‎таблицы‏ ‎страниц‏ ‎можно ‎было‏ ‎бы ‎избежать‏ ‎если ‎бы‏ ‎мы‏ ‎разделяли ‎пулы‏ ‎по ‎классу ‎размера. ‎И ‎конечно‏ ‎спорная ‎оптимизация‏ ‎«нет‏ ‎внутренних ‎указателей», ‎которая‏ ‎не ‎только‏ ‎дает ‎небезопасный ‎код ‎(объект‏ ‎может‏ ‎быть ‎собран,‏ ‎несмотря ‎на‏ ‎ссылки ‎на ‎него), ‎но ‎и‏ ‎добавляет‏ ‎дополнительные ‎проверки‏ ‎на ‎критическом‏ ‎пути ‎для ‎всех ‎больших ‎объектов,‏ ‎добавляя‏ ‎потенциальное‏ ‎чтение ‎битовой‏ ‎таблицы.

Что ‎же‏ ‎это ‎было‏ ‎довольно‏ ‎сложно, ‎но‏ ‎помните ‎что ‎mark ‎фаза ‎это‏ ‎сердце ‎любого‏ ‎сборщика.‏ ‎Теперь ‎к ‎третьей‏ ‎фазе ‎—‏ ‎sweep. ‎Иронично ‎sweep ‎не‏ ‎подчищает‏ ‎память ‎в‏ ‎free ‎list-ы‏ ‎как ‎можно ‎было ‎бы ‎ожидать.‏ ‎Вместо‏ ‎этого ‎все‏ ‎чем ‎она‏ ‎занимается ‎это ‎вызов ‎finalizer-ов ‎(если‏ ‎есть)‏ ‎и‏ ‎установкой ‎свободных‏ ‎битов ‎и‏ ‎прочих ‎таблиц.‏ ‎Имеем‏ ‎в ‎виду,‏ ‎что ‎при ‎этом ‎выполняется ‎линейный‏ ‎проход ‎по‏ ‎памяти‏ ‎каждого ‎пула ‎и‏ ‎просмотр ‎mark‏ ‎битов.

Финальная ‎стадия ‎— ‎recover,‏ ‎действительно‏ ‎строит ‎free‏ ‎list-ы. ‎Это‏ ‎опять ‎линейный ‎проход, ‎но ‎только‏ ‎по‏ ‎маленьким ‎пулам.‏ ‎Снова ‎чтение‏ ‎таблицы ‎страниц ‎для ‎каждой ‎страницы‏ ‎чтобы‏ ‎узнать‏ ‎класс ‎размера,‏ ‎просто ‎хочется‏ ‎плакать. ‎Но‏ ‎основной‏ ‎вопрос ‎без‏ ‎ответа ‎— ‎зачем? ‎Зачем ‎дополнительный‏ ‎проход? ‎Я‏ ‎сильно‏ ‎старался ‎чтобы ‎придумать‏ ‎разумное ‎объяснение,‏ ‎но ‎не ‎смог, ‎«для‏ ‎простоты»‏ ‎слабая, ‎но‏ ‎вероятная ‎причина.‏ ‎Это ‎последняя ‎большая ‎ошибка ‎по‏ ‎моим‏ ‎подсчетам.

Чего ‎здесь‏ ‎нет

До ‎сих‏ ‎пор ‎я ‎критиковал ‎то, ‎что‏ ‎видно‏ ‎невооруженным‏ ‎глазом, ‎теперь‏ ‎настало ‎время‏ ‎пройтись ‎по‏ ‎тому,‏ ‎чего ‎просто‏ ‎нет.

Thread ‎cache ‎явный ‎недосмотр, ‎имея‏ ‎в ‎виду‏ ‎что‏ ‎сборщик ‎мусора ‎D‏ ‎уходит ‎корнями‏ ‎в ‎ранние ‎2000ые ‎это‏ ‎не‏ ‎так ‎уж‏ ‎неожиданно. ‎Практически‏ ‎каждый ‎современный ‎аллокатор ‎имеет ‎некий‏ ‎thread‏ ‎cache, ‎некоторые‏ ‎пытаются ‎поддерживать‏ ‎по ‎процессорный ‎кеш. ‎Кеш ‎работает‏ ‎так,‏ ‎что‏ ‎каждый ‎thread‏ ‎выделяет ‎аллокации‏ ‎пачками, ‎сохраняя‏ ‎запас‏ ‎аллокаций ‎на‏ ‎будущее. ‎Это ‎амортизирует ‎цену ‎блокирования‏ ‎общих ‎структур‏ ‎кучи.‏ ‎Кстати ‎присутствует ‎немного‏ ‎fine ‎grained‏ ‎блокирование, ‎но ‎не ‎на‏ ‎уровне‏ ‎отдельных ‎пулов,‏ ‎к ‎примеру.

[Обновление‏ ‎2024] ‎Сборщик ‎D ‎получил ‎параллельный‏ ‎mark‏ ‎и ‎есть‏ ‎даже ‎версия‏ ‎concurrent ‎сборщика ‎для ‎Posix.

Параллельный ‎mark‏ ‎еще‏ ‎один‏ ‎пример ‎современной‏ ‎фичи, ‎которая‏ ‎ожидается ‎от‏ ‎любого‏ ‎сборщика ‎мусора.‏ ‎Также ‎популяны ‎concurrent ‎или ‎mostly‏ ‎concurrent ‎сборщики,‏ ‎в‏ ‎которых ‎mark ‎и‏ ‎немного ‎реже‏ ‎sweep/compaction ‎выполняются ‎одновременно ‎с‏ ‎работой‏ ‎пользовательских ‎потоков.

В‏ ‎заключение

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

Теперь ‎подведем‏ ‎итог ‎моей‏ ‎первой ‎итерации ‎изменений ‎по ‎сравнению‏ ‎с‏ ‎статус-кво.

  • Разделить‏ ‎маленькие ‎пулы‏ ‎по ‎классу‏ ‎размера.
  • Сделать ‎поиск‏ ‎пула‏ ‎O ‎(1).
  • Попытаться‏ ‎использовать ‎классы ‎размера ‎не ‎степеней‏ ‎двойки ‎как‏ ‎jemalloc,‏ ‎чтобы ‎побороть ‎внутренную‏ ‎фрагментацию.
  • Разделит ‎все‏ ‎пулы ‎на ‎основе ‎noscan‏ ‎атрибута,‏ ‎это ‎ускоряет‏ ‎mark.
  • Предусмотреть ‎3-ий‏ ‎класс ‎«пулов» ‎для ‎огромных ‎аллокаций‏ ‎рассчитанных‏ ‎только ‎на‏ ‎один ‎объект.
  • Аллокация‏ ‎объектов ‎для ‎больших ‎пуллов ‎требует‏ ‎проработки,‏ ‎нужно‏ ‎какое-то ‎дерево‏ ‎с ‎ключом‏ ‎по ‎размеру‏ ‎блока.‏ ‎jemallox ‎использует‏ ‎красно-черные ‎деревья.
  • Игнорировать ‎атрибут ‎«нет ‎внутренних‏ ‎указателей» ‎он‏ ‎пытается‏ ‎побороть ‎ложные ‎указатели‏ ‎по ‎причине‏ ‎консервативности ‎сборщика ‎мусора. ‎Однако‏ ‎это‏ ‎фундаментально ‎небезапасно‏ ‎и ‎цена‏ ‎слишком ‎высока, ‎ну ‎и ‎наконец,‏ ‎это‏ ‎абсолютно ‎бессмысленно‏ ‎на ‎64-битных‏ ‎системах, ‎где ‎живут ‎ваши ‎серверы.
  • Никаких‏ ‎сомнительных‏ ‎мультифазных‏ ‎сборок, ‎это‏ ‎mark ‎и‏ ‎sweep ‎и‏ ‎точка.
  • Fine‏ ‎grained ‎блокировки‏ ‎с ‎самого ‎начала, ‎я ‎не‏ ‎вижу ‎проблем‏ ‎с‏ ‎блокировками ‎по ‎отдельным‏ ‎пулам.

Вторая ‎итерация‏ ‎будет ‎сосредоточенна ‎на ‎более‏ ‎мясистых‏ ‎вещах ‎вроде‏ ‎thread ‎cache,‏ ‎параллельного ‎mark ‎и ‎concurrent ‎mark‏ ‎с‏ ‎использованием ‎fork.

Третья‏ ‎итерация, ‎если‏ ‎мы ‎до ‎нее ‎доберемся, ‎будет‏ ‎абсолютно‏ ‎новый‏ ‎дизайн ‎—‏ ‎mark-region ‎сборщик,‏ ‎с ‎архитектурой‏ ‎вдохновленной‏ ‎immix.

На ‎этом‏ ‎мои ‎планы ‎завершаются ‎и ‎на‏ ‎этой ‎оптимистичной‏ ‎ноте,‏ ‎я ‎должен ‎предупредить,‏ ‎что ‎поначалу‏ ‎это ‎будет ‎Linux ‎специфичный‏ ‎сборщик,‏ ‎постепенно ‎эволюционирующий‏ ‎до ‎любой‏ ‎Posix ‎системы, ‎а ‎поддержка ‎Windows‏ ‎является‏ ‎отдаленной ‎возможностью.



Следующий
Все посты проекта
0 комментариев

Статистика

6 500 ₽ всего собрано

Контакты

Метки

Подарить подписку

Будет создан код, который позволит адресату получить бесплатный для него доступ на определённый уровень подписки.

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

Будет создан код, который позволит адресату получить сумму на баланс.

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

Добавить карту
0/2048