Чому велика купка об’єктів і чому нас хвилює?


105

Я читав про покоління та великі об’єкти кучі. Але я все ще не розумію, у чому полягає значення (або користь) наявності великої об'єктної купи?

Що могло піти не так (з точки зору продуктивності чи пам’яті), якби CLR просто покладався на покоління 2 (враховуючи, що поріг для Gen0 та Gen1 малий для обробки великих об’єктів) для зберігання великих об’єктів?


6
Це дає мені два питання для .NET-дизайнерів: 1. Чому не викликається дефрагментація LOH перед тим, як викинути OutOfMemoryException? 2. Чому б об’єкти LOH не мали спорідненості залишатися разом (великі вважають за краще кінець купи, а малі на початку)
Jacob Brewer

Відповіді:


195

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

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

Тож вони провели купу орієнтирів, щоб визначити точку беззбитковості. І досяг 85 000 байтів як точка відсічення, коли копіювання більше не покращує перф. За винятком винятків для масивів подвійних, вони вважаються "великими", коли масив містить більше 1000 елементів. Це ще одна оптимізація для 32-розрядного коду, великий розподільник купок об'єктів має особливу властивість, що він виділяє пам'ять за адресами, вирівняними до 8, на відміну від звичайного генераційного розподільника, який виділяє лише вирівнювання до 4. Це вирівнювання є великою справою для подвійного , читання або написання неправильно вирівняного подвійного дуже дорого. Як не дивно, про рідкісну інформацію про Microsoft ніколи не згадуються масиви довгих, не впевнені, що з цим.

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

Проблема, яка в іншому випадку повністю зникає просто запуском коду в 64-бітній операційній системі. 64-бітний процес має 8 терабайт адресного простору віртуальної пам'яті, що на 3 порядки більше, ніж 32-бітний процес. Ви просто не можете закінчитися з дірочками.

Якщо коротко сказати, то LOH робить роботу коду більш ефективною. Ціною використання наявного адресного простору віртуальної пам'яті менш ефективно.


UPDATE, .NET 4.5.1 тепер підтримує ущільнення властивості LOH, GCSettings.LargeObjectHeapCompactionMode . Остерігайтеся наслідків, будь ласка.


3
@Hans Passant, чи можете ви уточнити про систему x64, ви маєте на увазі, що ця проблема повністю розчаровує?
Johnny_D

Деякі деталі реалізації LOH мають сенс, але деякі мене спантеличують. Наприклад, я можу зрозуміти, що якщо багато великих об’єктів створено та залишено, то, як правило, бажано видалити їх масово в колекції Gen2, ніж частинку в колекціях Gen0, але якщо один створює і відмовляється, наприклад, масив з 22000 рядків, до якого жодних зовнішніх посилань не існує, яка перевага у тому, щоб колекції Gen0 та Gen1 помітили всі 22000 рядків як "живі", без огляду на те, чи існує посилання на масив?
supercat

6
Звичайно, проблема з фрагментацією однакова на x64. Потрібно лише кілька днів більше запустити ваш серверний процес, перш ніж він розпочнеться.
Lothar

1
Хм, ні, ніколи не варто недооцінювати 3 порядки. Скільки часу потрібно, щоб сміття збирало 4 терабайтну купу - це те, чого ви не можете уникнути задовго до того, як вона наблизиться до цього.
Ганс Пасант

2
@HansPassant Ви можете, будь ласка, докладно додати цю заяву: "Скільки часу потрібно для сміття, щоб зібрати 4 терабайтну купу - те, чого ви не можете уникнути, щоб виявити задовго до того, як вона наблизиться до цього".
порівняно_випадково

9

Якщо розмір об'єкта перевищує деяке закріплене значення (85000 байт у .NET 1), то CLR розміщує його у великій купі об’єктів. Це оптимізує:

  1. Виділення об'єктів (малі об'єкти не змішуються з великими об'єктами)
  2. Збір сміття (LOH збирається лише на повній ГК)
  3. Дефрагментація пам'яті (LOH ніколи не рідко ущільнюється)

9

Суттєва відмінність Малої об’єктної купи (SOH) та Large Object Heap (LOH) полягає в тому, що пам'ять у SOH ущільнюється, коли збирається, а LOH - ні, як це показано у цій статті . Ущільнення великих об'єктів коштує багато. Аналогічно із прикладами у статті, наприклад, для переміщення байта в пам'ять потрібно 2 цикли, потім для ущільнення об'єкта 8 МБ в комп'ютері 2 ГГц потрібно 8 мс, що є великою вартістю. Зважаючи на те, що великі об'єкти (масиви в більшості випадків) є досить поширеними на практиці, я вважаю, що це причина, чому Microsoft записує великі об'єкти в пам'ять і пропонує LOH.

BTW, згідно з цим повідомленням , LOH зазвичай не створює проблем з фрагментами пам'яті.


1
Завантаження великої кількості даних в керовані об'єкти зазвичай зменшує вартість 8 мс для ущільнення LOH. На практиці у більшості додатків з великими даними вартість LOH є тривіальною поруч із рештою продуктивності програми.
Шив

3

Основне в тому, що малоймовірно (і цілком можливо, поганий дизайн) процес створить безліч короткоживучих великих об'єктів, тому CLR виділяє великі об'єкти в окрему купу, на якій він виконує GC за іншим графіком до звичайної купи. http://msdn.microsoft.com/en-us/magazine/cc534993.aspx


Крім того, якщо розмістити великі об'єкти, скажімо, покоління 2 може призвести до погіршення продуктивності, оскільки для компактної пам’яті знадобиться багато часу, особливо якщо було б звільнено невелику кількість, і ВЕЛИЧНІ об’єкти довелося скопіювати на нове місце. Поточний LOH не ущільнюється з міркувань продуктивності.
Крістофер Керренс

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


1
@CodeInChaos: Хоча система може мати сенс чекати, поки колекція gen2, перш ніж намагатися відновити пам'ять навіть з короткотривалих об’єктів LOH, я не бачу жодної переваги в оголошенні об’єктів LOH (і будь-яких об'єктів, до яких вони прихильні посилання) безумовно живуть під час колекцій gen0 та gen1. Чи є якісь оптимізації, які можливі завдяки такому припущенню?
supercat

@supercat Я переглянув посилання, згадане Майлесом МакДоннелом. Я розумію: 1. Колекція LOH відбувається в GC 2-го типу. 2. Колекція LOH не включає ущільнення (до моменту написання статті). Натомість він позначатиме мертві об’єкти як багаторазові, і ці отвори слугуватимуть майбутнім виділенням LOH, якщо вони досить великі. Зважаючи на пункт 1, враховуючи, що GC gen 2 буде повільним, якщо в ген 2 є багато об'єктів, я вважаю, що краще уникати використання LOH максимально в цьому випадку.
фанат грабі

0

Я не фахівець з CLR, але я б міг уявити, що наявність спеціальної купи для великих об'єктів може запобігти зайвим змістам GC існуючих поколінь. Виділення великої об'єкта вимагає значної кількості безперервної вільної пам'яті. Для того щоб переконатися, що з розсіяних «дірок» у поколіннях поколінь вам потрібні часті ущільнення (які робляться лише з циклами GC).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.