Як сміттєзбірник запобігає скануванню всієї пам’яті на кожен збір?


16

Деякі (принаймні, Mono і .NET) сміттєзбірники мають короткочасну область пам’яті, яку вони сканують часто, і вторинну область пам’яті, яку вони сканують рідше. Моно називає це розсадником.

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

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

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


1
приємний огляд - у статті «Тюнінг зі збирання сміття», написаній Анжелікою Лангер. Формально мова йде про те, як це робиться на Java, але представлені концепції в значній мірі агностичні
gnat

Відповіді:


14

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

  1. Після колекції всі об'єкти, які все ще існують, матимуть мінімальне покоління (наприклад, у .net, після колекції Gen0, всі об'єкти є Gen1 або Gen2; після колекції Gen1 або Gen2 всі об'єкти є Gen2).
  2. Об'єкт або його частина, яка не була написана з моменту, коли колекція, яка рекламувала все до покоління N або вище, не може містити посилань на об'єкти нижчих поколінь.
  3. Якщо об’єкт досяг певного покоління, його не потрібно ідентифікувати як доступний, щоб забезпечити його збереження при зборі нижчих поколінь.

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

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


переміщення блоків пам’яті навколо дорого в будь-якій системі, тому покращення розгортки - це виграш навіть у вашій Quad Ghz CPU-системі.
gbjbaanb

@gbjbaanb: У багатьох випадках вартість сканування всього, щоб знайти живі об'єкти, була б значною і заперечною, навіть якщо переміщення об'єктів було абсолютно безкоштовним. Отже, слід практично уникати сканування старих об'єктів. З іншого боку, утримання від ущільнення старих об'єктів - це проста оптимізація, яка може бути здійснена навіть на простих структурах. До речі, якщо хтось розробляв рамки GC для невеликої вбудованої системи, декларативна підтримка незмінних об'єктів може бути корисною. Важко відстежувати, чи змінився об'єкт, який змінюється, але важко зробити ...
supercat

... просто припустимо, що змінні об'єкти потрібно сканувати кожен пропуск GC, але незмінні об'єкти цього не роблять. Навіть якщо єдиним способом побудувати незмінний об'єкт було побудувати "прототип" у мінливому просторі та потім скопіювати його, одна операція додаткової копії може уникнути необхідності сканувати об'єкт у майбутніх операціях GC.
supercat

Між іншим, продуктивність збору сміття в 1980-х роках впровадженнях BASIC для 6502 мікропроцесорів (а можливо, і інших) може бути значно покращена в деяких випадках, якби програма, яка створила безліч рядків, які ніколи не змінилися, скопіювала "наступний розподіл рядків "вказівник на покажчик" простір у верхній частині рядка ". Така зміна завадить сміттєзбірнику вивчити будь-яку зі старих рядків, щоб перевірити, чи вони ще потрібні. Commodore 64 навряд чи був високотехнологічним, але така "покоління" GC допомогла б навіть там.
supercat

7

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

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


3

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


1

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

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


Я не впевнений, які підходи до збирання сміття використовувались у мінікомп'ютерах та мейнфреймах до кінця 1970-х, але збирач сміття Microsoft BASIC, принаймні на 6502 машинах, встановив би свій "наступний рядок" вказівник на верхню частину пам'яті, а потім шукати всі посилання рядків, щоб знайти найвищу адресу, що була нижче "наступного вказівника рядка". Цей рядок буде скопійовано трохи нижче "наступного вказівника рядка", а цей вказівник буде припаркований трохи нижче нього. Потім алгоритм повториться. Можна було за допомогою коду супроводжувати покажчики надати ...
supercat

... щось на кшталт колекції поколінь. Іноді я замислювався про те, як важко було б зафіксувати BASIC для впровадження "покоління" колекції, просто зберігаючи адреси верхньої частини кожного покоління та додаючи кілька операцій підміни вказівників до та після кожного циклу GC. Продуктивність GC все ще буде досить поганою, але в багатьох випадках може бути поголена від десятків секунд до десятих секунд.
supercat

-2

В основному ... GC використовує "відра", щоб розділити те, що використовується, а що ні. Після того, як він перевіряє, він видаляє речі, які не використовуються, і переміщує все інше до другого покоління (яке перевіряється рідше, ніж 1-го покоління), а потім переміщує речі, які все ще використовуються у 2-му дені до 3-го покоління.

Отже, речі 3-го покоління - це зазвичай предмети, які чомусь застрягли, і GC не перевіряє їх там дуже часто.


1
Але як дізнатися, які об’єкти використовуються?
Пітер ван Гінкель

Він відслідковує, які об’єкти можуть бути доступні за допомогою доступного коду. Після того, як об’єкт більше не буде доступний для будь-якого коду, який можна виконати (скажімо, код для повернутого методу), тоді GC знає, що його можна збирати
JohnL

Ви, хлопці, описуєте, як ГК правильно, а не наскільки вони ефективні. Судячи з питання, ОП це добре знає.

@delnan так, я відповідав на питання, як воно знає, які об’єкти використовуються, що було в коментарі Пітера.
JohnL

-5

Алгоритм, який зазвичай використовує цей GC, - це " Naive" -ознайомлення

Ви також повинні знати про те, що цим керує не сам C #, а так званий CLR .


Це відчуття, яке я отримав, читаючи про сміттєзбірник Mono. Однак те, що я не розумію, це те, чому якщо вони сканують повний робочий набір коли-небудь збирати, у них є генераційний колектор, з яким GEN-0 збирає його дуже швидко. Як це коли-небудь може бути швидким за допомогою робочого набору скажімо 2 Гб?
Пітер ван Гінкель

ну справжній GC для моно - Sgen, ви повинні прочитати цей mono-project.com/Generational_GC або деякі онлайн-статті schani.wordpress.com/tag/mono infoq.com/news/2011/01/SGen , справа в тому, що такі нові технології, як CLR та CLI, мають дійсно модульну конструкцію, мова стає просто способом висловити щось для CLR, а не способом створення бінарного коду. Ваше запитання стосується деталей реалізації, а не алгоритмів, оскільки алгоритм все ще не має реалізації, вам слід просто прочитати технічні документи та статті від Mono, ні з ким більше.
користувач827992

Я збентежений. Стратегія, яку використовує сміттєзбірник, - це не алгоритм?
Пітер ван Гінкель

2
-1 Перестаньте плутати OP. Те, що GC є частиною CLR, а не мовою, зовсім не має значення. АЯ в основному характеризуються тим, як він виймає купу і визначає досяжність, а останнім все про алгоритм (и) , що використовується для цього. Хоча може бути багато реалізацій алгоритму, і вам не слід зациклюватися на деталях реалізації, лише алгоритм визначає, скільки об'єктів сканується. Покоління GC - це просто алгоритм + компонування купи, яка намагається використовувати "генераційну гіпотезу" (що більшість об'єктів гине молодими). Це не наївні.

4
Алгоритм! = Реалізація справді, але реалізація може відхилитися лише від цього далеко до того, як стане реалізацією іншого алгоритму. Опис алгоритму в світі GC дуже специфічний і включає такі речі, як не сканування всієї купи в дитячій колекції та те, як знаходяться та зберігаються міжпоколільні покажчики. Це правда, що алгоритм не каже вам, скільки часу буде тривати конкретний крок алгоритму, але це зовсім не стосується цього питання.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.