Як сміттєзбірники уникають переповнення штабелю?


23

Тож я думав про те, як працюють сміттєзбірники, і думав про цікаве питання. Імовірно, збирачі сміття повинні перетинати всі споруди однаковим чином. Вони не можуть знати погоди, яку вони об’їжджають пов’язаним списком чи збалансованим деревом чи ще. Вони також не можуть використовувати занадто багато пам'яті для пошуку. Один з можливих способів, і єдиний спосіб, яким я можу подумати, щоб перетнути ВСІ структури, може бути просто перетинати їх усі рекурсивно так, як ви б бінарне дерево. Це, однак, призведе до переповнення стека у пов'язаному списку або навіть просто погано збалансованому бінарному дереві. Але всі мови, зібрані сміттям, які я коли-небудь використовував, здаються, не мають жодних проблем в таких випадках.

У книзі-драконі використовується сортування "Несканковані" черги. В основному замість того, щоб рекурсивно переходити до структури, вона просто додає речі, які потрібно позначити занадто чергою, а потім для кожної речі, не зазначеної в кінці, вона видаляється. Але хіба ця черга не стане дуже великою?

Отже, як збирачі сміття перетинають довільні структури? Як ця техніка обходу уникає переповнення?


1
GC перетинає всі структури більш-менш однаково, але лише в дуже абстрактному сенсі (див. Відповідь). Те, як вони конкретно відстежують річ, набагато складніше, ніж зазначено в елементарних презентаціях, які ви можете знайти в підручниках. І вони не використовують рекурсії. Крім того, у віртуальній пам'яті погані реалізації виявляються як уповільнення GC, рідко як переповнення пам'яті.
babou

Ви турбуєтесь про місце, необхідне для відстеження. Але як бути з простором або структурами, необхідними для того, щоб відрізнити відстежувану і, як відомо, використовується, пам'ять, від пам'яті, яка потенційно відновлюється. Це може мати значну вартість пам'яті, можливо пропорційну розміру купи.
babou

Я подумав, що це буде зроблено за допомогою бітвектора на розмір об'єкта більше 16 байт, щоб над головою було як мінімум в 1000 разів менше.
Джейк

Існує багато способів зробити це (див. Відповідь), і вони також можуть бути використані для відстеження, що потім відповість на ваше запитання (біттектори або растрові карти можуть бути використані для відстеження, а не стек чи черга, яку ви запропонували). Ви не можете вважати, що всі об'єкти є великими, якщо ви не збираєтесь витрачати місця на невеликі об'єкти, яких може бути багато, і тоді ви не повинні турбуватися про місце. Якщо ви знаходитесь у віртуальній пам'яті, простору часто набагато менше, а проблеми дуже різні.
babou

Відповіді:


13

Зауважте, що я не фахівець зі збору сміття. Ця відповідь дає лише приклади прийомів. Я не стверджую, що це репрезентативний огляд методів вивезення сміття.

Несканована черга - звичайний вибір. Черга може бути великою - потенційно такою ж великою, як і найглибша структура даних. Черга, як правило, зберігається явно, а не в стопці нитки для збирання сміття.

Після того, як всі, крім одного з вузлів, скановані, вузол можна буде видалити з черги, що не сканується. Це в основному оптимізація хвостових викликів. Сміттєзбірники можуть включати евристику для спроби останнього сканування найглибшого дочірнього вузла; наприклад, GC для Lisp повинен сканувати на cara consдо cdr.

Один із способів уникнути збереження чергової черги - це змінити покажчики на місці, змусивши дитину тимчасово вказувати на батьків. Це техніка переходу дерева постійної пам'яті, яка використовується в інших контекстах, ніж сміттєзбірники. Мінус цієї методики полягає в тому, що, поки GC перебуває через структуру даних, структура даних недійсна, тому GC повинен зупинити світ. Це не є вимикачем угод: багато сміттєзбірників включають фазу, яка зупиняє світ, на додаток до фаз, які не можуть, але можуть пропустити сміття в результаті.


2
Техніка, описана в останньому абзаці, часто називається " переворот вказівника ".
Блукаюча логіка

@WanderingLogic Так, розворот вказівника - це, як я його назвав у власній відповіді. Це пов'язано з Deutsch, Schorr і Waite (1967). Однак невірно стверджувати, що вона працює в постійній пам'яті: для неї потрібні додаткові біти для кожної комірки з p покажчиками, хоча це можна зменшити за допомогою бітових стеків. Прийнята відповідь, на яку ви посилаєтесь, не зовсім коректна або повна з тієї ж причини. журнал2pp
бабу

Я вже використовував розворот покажчика в призначеному для користувача GC без необхідності цих додаткових біт; фокус полягав у використанні спеціального в пам’яті подання об’єктів у пам’яті. А саме, об’єкт "заголовок" знаходився посередині, з полями вказівника перед заголовком, а поля, які не вказували після,; крім того, всі покажчики були вирівняні, і заголовок включав поле з найменш значущим бітом, який завжди встановлюється. Таким чином, під час зворотного відстеження вказівника, досягнення наступного вказівника і помічаючи, що ми закінчили об'єкт, можна зробити однозначно без зайвих бітів. Цей макет також підтримував спадкування OOP.
Томас Порнін

@ThomasPornin Я думаю, що бітова інформація повинна бути десь. Питання де? Чи можемо ми обговорити це у чаті? Я маю зараз піти, але я хотів би дійти до цього. Або в Інтернеті доступний опис?
бабу


11

Коротше кажучи : сміттєзбірники не використовують рекурсії. Вони просто керують трасуванням, відслідковуючи фактично два набори (які можуть поєднуватися). Порядок відстеження та обробки осередків не має значення, що дає значну свободу реалізації для представлення наборів. Отже, існує багато рішень, які насправді дуже дешеві у використанні пам'яті. Це важливо, оскільки GC називається саме тоді, коли в купі не вистачає пам'яті. Речі дещо відрізняються з великими віртуальними пам’ятками, оскільки нові сторінки можна легко виділити, а враження - це не брак місця, а відсутність локальності даних .

Я припускаю, що ви розглядаєте питання про пошук сміттєзбірників, не рахуючи посилань, на які, схоже, ваше питання не стосується.

Питання фокусується на вартості пам'яті відстеження для відстеження набору: множина U (для необробленого) доступних комірок пам’яті, які все ще містять покажчики, які ще не відстежені. Це лише половина проблеми з пам’яттю для вивезення сміття. GC також повинен відслідковувати інший набір: набір (для відвідуваних) усіх комірок, які виявилися доступними, щоб повернути всі інші комірки в кінці процесу. Обговорювати одне, а не інше має обмежений сенс, оскільки вони можуть мати подібну вартість, використовувати подібні рішення і навіть поєднуватись.V

Перше, що слід зауважити, - це те, що всі трасируючі GC дотримуються тієї ж абстрактної моделі, що базується на систематичному дослідженні спрямованого графіка комірок у пам'яті, доступному з програми, де комірки пам'яті є вершинами, а покажчики - напрямними краями. Для цього використовуються такі набори:

  • набір (відвіданих) комірок, які вже виявилися доступними мутатором , тобто програма або алгоритм, для якого виконується GC. Множина V розділена на дві роз'єднані підмножини: V = U T ;VVV=UТ

  • безліч (без відстеження) відвідуваних осередків з покажчиками, які ще не були виконані;U

  • безліч (простежено) відвідуваних осередків, на яких були простежені всі їх вказівники.Т

  • ми також відзначимо Н

Тільки і U , або U іVUUТ

Алгоритм починається з деяких кореневих покажчиків, відомих системі запуску часу (як правило, покажчики в пам'яті, виділеній стеком), і розміщує всі комірки, які вони вказують, у нетренований набір (звідси вUV

Потім колектор приймає осередки в по черзі і перевіряє на кожну клітинку c всі її вказівники. Для кожного вказівника, якщо загострена комірка знаходиться у V , тоді нічого не робиться, інакше загострена комірка додається до U , оскільки її покажчики ще перевірені. Коли всі його покажчики були оброблені, комірка c переноситься з непрограмованого набору U у відстежений набірUcVUcUТ

Трасування закінчується, коли порожній. Це має відбутися, оскільки жодна комірка не проходить через U більше одного разу. У цьому моменті V = T , і всі комірки в V, як відомо, є доступними для програми, тому не підлягають відшкодуванню. Доповнення H - V офUUV=ТVН-VV

Детально, деталі можуть залежати від того, як реалізуються набори, а також від того, чи є це і U , або U іVUUТ

Я також пропускаю деталі про те, що таке клітинка, чи є вони в одному розмірі чи багато, як ми знаходимо в них покажчики, як вони можуть бути ущільнені, та ще безліч інших технічних питань, які ви можете знайти в книгах та опитуваннях щодо збору сміття .

Можливо, ви помітили, що це надзвичайно простий алгоритм. Не існує рекурсії, а лише цикл на елементах множини який може зростати в міру його обробкиU , поки з часом він не спорожняється. Немає апріорного припущення про додаткову пам'ять. Все, що дозволяє ідентифікувати набори, і робити досить дешево, необхідні операції. Зауважте, що порядок оброблення комірок не має значення (немає конкретної потреби у складі висувного стеку), що дає багато свободи для вибору засобів для ефективного представлення наборів.

Там, де відомі реалізації відрізняються, полягає в тому, як насправді представлені ці набори. Насправді було використано багато методик:

  • бітова карта: деякий простір пам’яті зберігається для карти, яка містить один біт для кожної комірки пам’яті, який можна знайти за допомогою адреси комірки. Біт увімкнено, коли відповідна комірка знаходиться у наборі, визначеному картою. Якщо використовуються лише бітові карти, вам потрібно лише 2 біти на комірку.

  • також ви можете мати місце для спеціального біта тегів (або 2) у кожній комірці, щоб позначити його.

  • журнал2pp - кількість покажчиків на комірку, а це ще більше зменшується за допомогою стеків бітів.

  • ви можете перевірити присудок на вміст клітини та її вказівники.

  • Ви можете перенести комірку у вільну частину пам'яті, призначену для всіх лише осередків, що належать до представленого набору.

  • VТТU .

  • ви можете комбінувати ці методи навіть для одного набору.

Як було сказано, все вищезазначене використовувалося деяким реалізованим сміттєзбірником, як це не здається дивним. Все залежить від різних обмежень реалізації. І вони можуть бути досить дешевими у використанні пам’яті, можливо, допомагають при обробці політики замовлення яку можна вільно обирати для цієї мети, оскільки вони не мають значення для кінцевого результату.

Що може здатися найдивнішим, перенесення комірок на нову область насправді дуже поширене: воно називається колекцією копій. В основному використовується з віртуальною пам'яттю.

Зрозуміло, що рекурсії немає, і стек алгоритму мутатора використовувати не потрібно.

Ще одним важливим моментом є те, що багато сучасних ГК реалізовані для великих віртуальних пам’яток . Тоді отримання місця для реалізації та додаткового списку чи стеку не є проблемою, оскільки нові сторінки можна легко виділити. Однак у великих віртуальних спогадах еннемія - це не брак місця, а відсутність локальності . Тоді структура, що представляє набори, та їх використання повинні бути спрямовані на збереження місцевості структури даних та виконання GC. Проблема - не простір, а час. Неадекватна реалізація, швидше за все, виявить неприйнятне уповільнення, ніж переповнення пам'яті.

Я не давав посилань на безліч конкретних алгоритмів, що виникають внаслідок різних комбінацій цих методик, оскільки це здається досить довгим.


4

Стандартний спосіб уникнути переповнення стека - використовувати явний стек (зберігається як структура даних у купі). Це працює і для цих цілей. У сміттєзбірників часто є робочий список предметів, які потрібно оглянути / пройти, що виконує цю роль. Наприклад, ваша черга "Не сканований" є прикладом саме такого шаблону. Черга потенційно може бути великою, але це не спричиняє переповнення стека, оскільки вона не зберігається в сегменті стека. У будь-якому випадку він ніколи не стане більшим за кількість живих об’єктів у купі.


Коли викликається GC, купа зазвичай повна. Інший момент полягає в тому, що трапляється, що стек і купа ростуть з обох кінців одного і того ж простору пам'яті ..
babou

4

У "класичних" описах збирання сміття (наприклад, Марк Вілсон, " Техніка збору сміття Uniprocessor », Int'l Workshop з управління пам’яттю , 1992 р. ( Альтернативна посилання ), або в описі сучасної компіляторної програми Ендрю Аппела (Cambridge University Press, 1998)) колекціонерів класифікують як "Позначити і змітати", або "Копіювати".

Колектори Mark and Sweep уникають потребу в додатковому просторі, використовуючи розворот вказівника, як описано у відповіді @ Gilles. Аппел каже, що Кнут приписує алгоритм розвороту покажчика Пітеру Дойчу та Герберту Шорру та В.М. Уайте.

Копіюючи сміттєзбірники використовують те, що часто називають алгоритмом Чейні, щоб виконувати обхід черги, не потребуючи додаткового місця. Цей алгоритм був введений у CJ Cheyney, "Нерекурсивний список алгоритму ущільнення", Comm. ACM , 13 (11): 677-678, 1970.

У копіювальному смітнику у вас є шматок пам'яті, який ви намагаєтеся зібрати, званий з-простору , і шматок пам'яті, який ви використовуєте для копій, що називаються космос . Космічний простір організовано у вигляді черги з scanвказівником, що вказує на найдавніший скопійований, але незасканований запис, та freeвказівником, що вказує на наступну вільну позицію у космосі. Малюнок цього з статті Вілсона:

Приклад алгоритму Чейні

Під час сканування кожного елемента в космос ви копіюєте його дітей з-за простору на freeвказівник у-простір, а потім змінюєте вказівник на дитину з-пробілу на нову копію дитини в простір. Є додатковий трюк, який потрібно використовувати, коли ваші структури даних не є деревами (коли дитина може мати більше одного батька). У такому випадку, коли ви копіюєте дитину з космосу в космос, вам потрібно перезаписати стару версію дитини з вказівником для переадресації на нову копію дитини. Тоді, якщо ви коли-небудь скануєте інший вказівник на стару версію дитини, ви зрозумієте, що вона вже скопійована, і не копіюйте знову.


Насправді, як пояснено у моїй відповіді, колекція Mark + Sweep та Copy - це той самий абстрактний алгоритм графіків. Колекція MS та Copy відрізняється лише тим, як реалізуються набори, використовувані абстрактним алгоритмом, і обидві сім’ї включаються, з багатьма варіантами, в деяку комбінацію наборів методів реалізації, які я описую у своїй відповіді. Деякі варіанти GC насправді змішують MS та Copy в одному GC. Розділення MS та Copy дехто розглядає як зручний спосіб структурування книг, але це довільне, і я вважаю застаріле бачення.
бабу

@babou: Якщо ви використовуєте алгоритм копіювання, у якому все, що відвідується, буде скопійовано (повільно, але може бути корисно на невеликих платформах, де робочий набір ніколи не є таким великим), деякі алгоритми можуть бути дещо спрощені, оскільки можна використовувати пам'ять, яку раніше займав переміщений об'єкт як скретч. Можна також отримати обмежену можливість, щоб інші потоки виконували доступ лише до читання об'єктів під час збирання, за умови, що один перевіряє дійсність об'єкта до та після кожного зчитування та слідкує за покажчиком переадресації, якщо об’єкт перемістився.
supercat

@supercat Я не впевнений, що ти намагаєшся сказати, який твій намір. Деякі ваші твердження здаються правильними. Але я не розумію, як можна використовувати з космосу до закінчення циклу GC (він містить покажчики переадресації). І це було б подряпиною для чого? Як спростити алгоритм? Що стосується декількох мутаційних потоків, що виконуються під час GC, то це значною мірою є ортогональною проблемою, хоча це може серйозно вплинути на реалізацію. Я б не намагався цього звертатись у коментарях. Це повинно створювати менше проблем у доступі лише для читання, але чорт у деталях.
бабу
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.