Найкраща практика примусового збору сміття в C #


118

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

Відповіді:


112

Найкраща практика - не примушувати вивезення сміття.

За даними MSDN:

"Змусити вивезення сміття можна, зателефонувавши Collect, але більшість часу цього слід уникати, оскільки це може створити проблеми з продуктивністю."

Однак якщо ви зможете надійно протестувати свій код, щоб підтвердити, що виклик Collect () не матиме негативного впливу, тоді продовжуйте ...

Просто спробуйте переконатися, що об’єкти очищені, коли вони вам більше не потрібні. Якщо у вас є власні об’єкти, погляньте на використання оператора "using" та інтерфейсу IDisposable.

На цьому посиланні є кілька корисних практичних порад щодо звільнення пам’яті / вивезення сміття тощо:

http://msdn.microsoft.com/en-us/library/66x5fx1b.aspx


3
Крім того, ви можете встановити різні файли msdn.microsoft.com/en-us/library/bb384202.aspx
David d C e Freitas

4
Якщо ваші об’єкти вказують на некеровану пам'ять, ви можете повідомити про це збирач сміття через GC.AddMemoryPressure Api ( msdn.microsoft.com/en-us/library/… ). Це дає сміттєзбірнику більше інформації про вашу систему, не втручаючись в алгоритми збору.
Govert

+1: * погляньте на використання "statement" та інтерфейсу IDisposable. * Я б навіть не розглядав примушування, окрім як в крайньому випадку - гарну пораду (читайте як "відмова від відповідальності"). Я, однак, змушую колекцію в одиничному тесті імітувати втрату активної орієнтації в бек-енд-операції - врешті-решт, кидаю a TargetOfInvocationNullException.
IАнотація

33

Подивіться на це так - чи ефективніше викидати кухонне сміття, коли сміттєва бачка становить 10%, або нехай вона заповнює, перш ніж вивозити її?

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

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

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


5
Якщо б у вас народилася дитина, яка б померла, якби ви залишили її більше хвилини, а у вас тільки коли-небудь була хвилина, щоб впоратися зі сміттям, то вам би хотілося робити щоразу трохи замість усіх одразу. На жаль, метод GC :: Collect () не швидше, тим частіше ви його називаєте. Тож для двигуна в реальному часі, якщо ви не можете просто скористатися механізмом утилізації та дозволити GC об'єднати ваші дані, тоді ви не повинні використовувати керовану систему - відповідно до моєї відповіді (напевно, нижче цієї, лол).
Цзінь

1
У моєму випадку я запускаю алгоритм A * (найкоротший шлях) ПОВТОРЕННО для налаштування його продуктивності ... який у виробництві буде запущений лише один раз (на кожній "карті"). Тому я хочу, щоб GC було зроблено перед кожною ітерацією, поза моїм "блоком вимірювання продуктивності", тому що я вважаю, що більш детально моделює ситуацію у виробництві, в якій GC можна було / слід примусити після навігації по кожній "карті".
corlettk

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

32

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

Є кілька випадків, коли ви знаєте більше про використання пам’яті, а потім збирач сміття. Це навряд чи може бути правдою для багатокористувацької програми або сервісу, який відповідає на більше ніж один запит.

Однак у деяких обробках типу партії ви знаєте більше, ніж GC. Наприклад, розгляньте програму, яка.

  • Дається список імен файлів у командному рядку
  • Обробляє один файл, а потім записує результат у файл результатів.
  • Під час обробки файлу створюється безліч взаємопов'язаних об'єктів, які неможливо зібрати, поки обробка файлу не завершиться (наприклад, дерево розбору)
  • Не зберігає великого стану між обробленими файлами .

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

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

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

Я б скоріше мав API збору сміття, коли я міг би давати йому підказки щодо такого типу речей без того, щоб змушувати GC себе.

Дивіться також " Підказки виступу Ріко Маріані "


2
Аналогія: Playschool (система) зберігає олівці (ресурси). Залежно від кількості малюків (завдань) та дефіциту кольорів, вчитель (.Net) вирішує, як розподілити та поділити їх серед малюків. Коли запитується рідкісний колір, вчитель може виділити з пулу або шукати той, який не використовується. Вчитель має право на розсуд періодично збирати невикористані олівці (збирати сміття), щоб тримати речі в порядку (оптимізувати використання ресурсів). Як правило, батько (програміст) не може визначити політику прибирання кращих олівців у класі. Запланований дрімота для однієї дитини навряд чи буде сприятливим моментом, щоб перешкодити забарвленню інших дітей.
AlanK

1
@AlanK, мені це подобається, тому що коли діти йдуть додому на день, це дуже гарний час для помічників вчителів, щоб зробити гарненьке прибирання, не заважаючи дітям. (Останні системи, над якими я працював, щойно перезапустили процес обслуговування в такі часи, замість того, щоб змусити GC.)
Ян Рінроуз

21

Я думаю, що приклад, запропонований Ріко Мар'яні, був хорошим: можливо, доцільно запустити GC, якщо відбудеться значна зміна стану програми. Наприклад, в редакторі документів може бути нормально запускати GC, коли документ закритий.


2
Або безпосередньо перед відкриттям великого суміжного об'єкта, який виявив історію відмови і не представляє ефективного рішення для підвищення його деталізації.
crokusek

17

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

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

Проблеми з програмуванням дуже різноманітні і вимагають гнучких підходів. Я бачив випадки, коли є сенс блокувати GC у зібраних сміттям мовах та місцях, де є сенс викликати його, а не чекати, коли це відбуватиметься природним шляхом. У 95% випадків будь-який із них був би покажчиком неправильного підходу до проблеми. Але 1 раз у 20, мабуть, має бути зроблено справжній випадок.


12

Я навчився не намагатися перехитрити сміття. Зважаючи на це, я просто дотримуюся використання usingключового слова при роботі з некерованими ресурсами, такими як введення / виведення файлів або підключення до бази даних.


27
компілятор? що компілятор має стосунок до GC? :)
KristoferA

1
Нічого, він лише компілює та оптимізує код. І це однозначно не має нічого спільного з CLR ... або .NET навіть.
Кон

1
Об'єкти, що займають багато пам’яті, як дуже великі зображення, можуть не отримати збирання сміття, якщо ви явно не збираєте їх. Я думаю, що це (великі об'єкти) було питанням ОП, більш-менш.
code4life

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

9

Не впевнений, чи це найкраща практика, але працюючи з великою кількістю зображень у циклі (тобто створюючи та розміщуючи багато об’єктів Graphics / Image / Bitmap), я регулярно даю GC.Collect.

Думаю, я десь прочитав, що GC працює лише тоді, коли програма (в основному) працює в режимі очікування, а не посеред інтенсивного циклу, так що це може виглядати як область, де керівництво GC могло мати сенс.


Ви впевнені, що вам це потрібно? GC буде збирати , якщо він потребує в пам'яті, навіть якщо ваш код не простоює.
Конрад Рудольф

Не впевнений, як це зараз у .net 3.5 SP1, але раніше (1.1, і я вважаю, що я протестував проти 2.0), це змінило використання пам'яті. GC, звичайно, завжди збирає при необхідності, але ви все-таки зможете витратити 100 Мп оперативної пам’яті, коли вам потрібно лише 20. Хоча вам знадобиться ще кілька тестів
Michael Stum

2
GC спрацьовує при розподілі пам'яті, коли генерація 0 досягає певного порогу (наприклад, 1 МБ), а не коли "щось простоює". В іншому випадку ви можете закінчити OutOfMemoryException у циклі, просто розподіливши та негайно відкинувши об'єкти.
liggett78

5
100 мег оперативної пам’яті не витрачаються даремно, якщо не потрібні інші процеси. Це приємно збільшує продуктивність :-P
Orion Edwards

9

Нещодавно я стикався з тим, що потрібні дзвінки вручну GC.Collect()були під час роботи з великими об'єктами C ++, які були загорнуті в крихітні керовані об'єкти C ++, до яких, у свою чергу, зверталися із C #.

У сміттєзбірник ніколи не дзвонили, оскільки обсяг використаної керованої пам’яті був незначним, але кількість використаної некерованої пам’яті була величезною. Ручне заклик Dispose()до об'єктів вимагатиме відстеження того, коли об’єкти більше не потрібні, тоді як дзвінки GC.Collect()очистять усі об'єкти, які більше не передаються .....


6
Кращий спосіб вирішити це - зателефонувавши GC.AddMemoryPressure (ApproximateSizeOfUnmanagedResource)в конструктор і пізніше GC.RemoveMemoryPressure(addedSize)у фіналізатор. Таким чином сміттєзбірник буде працювати автоматично, враховуючи розмір некерованих структур, які можна було б зібрати. stackoverflow.com/questions/1149181 / ...
HugoRune

А ще кращий спосіб вирішити проблему - це фактично зателефонувати Dispose (), що ви, як і раніше, повинні зробити.
fabspro

2
Найкращий спосіб - використовувати структуру Using. Спробуйте / Нарешті. Вирішуйте клопоту
TamusJRoyce

7

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

  1. Чи є у вашому коді щось, що декларує елементи в більшому обсязі, ніж потрібно
  2. Чи справді використання пам'яті занадто велике
  3. Порівняйте продуктивність до та після використання GC.Collect (), щоб побачити, чи справді це допомагає.

5

Припустимо, у вашій програмі немає витоку пам’яті, об’єкти накопичуються і не можуть бути GC-ed у Gen 0, оскільки: 1) на них посилаються тривалий час, тому потрапляйте до Gen1 & Gen2; 2) Це великі об'єкти (> 80K), тому потрапляйте в LOH (Large Object Heap). І LOH не робить ущільнення, як у Gen0, Gen1 та Gen2.

Перевірте лічильник ефективності ".NET Memory", чи можете ви побачити, що 1) проблема насправді не є проблемою. Як правило, кожен 10 Gen0 GC запускає 1 Gen1 GC, а кожен 10 Gen1 GC запускає 1 Gen2 GC. Теоретично, GC1 та GC2 ніколи не можуть бути GC-ed, якщо не існує тиску на GC0 (якщо використання пам'яті програми дійсно провідне). Зі мною цього ніколи не буває.

Для проблеми 2) ви можете перевірити лічильник продуктивності ".NET Memory", щоб перевірити, чи втрачається LOH. Якщо це справді проблема з вашою проблемою, можливо, ви можете створити пул з великими об'єктами, як пропонує цей блог http://blogs.msdn.com/yunjin/archive/2004/01/27/63642.aspx .


4

Великі об’єкти розподіляються на LOH (велика купа об'єктів), а не на gen 0. Якщо ви говорите, що вони не збирають сміття разом із gen 0, ви маєте рацію. Я вважаю, що вони збираються лише тоді, коли відбувається повний цикл ГК (покоління 0, 1 і 2).

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

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


4

Я хотів би додати, що: Виклик GC.Collect () (+ WaitForPendingFinalizers ()) - це одна частина історії. Як справедливо зазначають інші, GC.COllect () - це недетермінований збір і залишається на розсуд самого GC (CLR). Навіть якщо ви додасте виклик до WaitForPendingFinalizers, він може не бути детермінованим. Візьміть код із цього посилання msdn та запустіть код з ітерацією циклу об'єкта як 1 або 2. Ви знайдете, що означає недетермінований (встановіть точку розриву в деструкторі об'єкта). Саме деструктор не викликається, коли Wait .. () було лише 1 (або 2) затримані об'єкти. [Citation reqd.]

Якщо ваш код має справу з некерованими ресурсами (напр., Зовнішні файлові ручки), ви повинні впровадити деструктори (або фіналізатори).

Ось цікавий приклад:

Примітка : Якщо ви вже спробували наведений вище приклад з MSDN, наступний код очистить повітря.

class Program
{    
    static void Main(string[] args)
        {
            SomePublisher publisher = new SomePublisher();

            for (int i = 0; i < 10; i++)
            {
                SomeSubscriber subscriber = new SomeSubscriber(publisher);
                subscriber = null;
            }

            GC.Collect();
            GC.WaitForPendingFinalizers();

            Console.WriteLine(SomeSubscriber.Count.ToString());


            Console.ReadLine();
        }
    }

    public class SomePublisher
    {
        public event EventHandler SomeEvent;
    }

    public class SomeSubscriber
    {
        public static int Count;

        public SomeSubscriber(SomePublisher publisher)
        {
            publisher.SomeEvent += new EventHandler(publisher_SomeEvent);
        }

        ~SomeSubscriber()
        {
            SomeSubscriber.Count++;
        }

        private void publisher_SomeEvent(object sender, EventArgs e)
        {
            // TODO: something
            string stub = "";
        }
    }

Я пропоную спочатку проаналізувати, який може бути вихід, а потім запустити, а потім прочитайте причину нижче:

{Деструктор викликається лише неявно після закінчення програми. } Щоб детерміновано очистити об'єкт, потрібно реалізувати IDisposable і зробити явний виклик Dispose (). У цьому суть! :)


2

Ще одне, що запускати GC Collect явно НЕ покращує продуктивність вашої програми. Це цілком можливо зробити гірше.

.NET GC добре розроблений і налаштований на адаптивність, а це означає, що він може регулювати поріг GC0 / 1/2 відповідно до "звички" використання вашої пам'яті програми. Отже, вона буде адаптована до вашої програми через деякий час роботи. Після виклику GC.Collect явно пороги будуть скинуті! І .NET повинен витратити час, щоб знову адаптуватися до "звички" вашої програми.

Моя пропозиція завжди довіряти .NET GC. Будь-яка проблема з пам'яттю, перевірте лічильник продуктивності ".NET Memory" та діагностуйте власний код.


6
Я думаю, що краще з’єднати цю відповідь з попередньою відповіддю.
Саламандер2007

1

Не впевнений, чи це найкраща практика ...

Пропозиція: не впевнено виконувати це чи що-небудь, коли не впевнені. Переоцініть, коли відомі факти, а потім виконайте перед / після тесту на ефективність, щоб перевірити.


0

Однак якщо ви зможете надійно протестувати свій код, щоб підтвердити, що виклик Collect () не матиме негативного впливу, тоді продовжуйте ...

IMHO, це схоже на вислів "Якщо ви зможете довести, що у вашій програмі ніколи не буде помилок, то продовжуйте ..."

По всій серйозності, примушування GC корисно для налагодження / тестування. Якщо вам здається, що вам потрібно це робити в будь-який інший час, то або ви помиляєтесь, або ваша програма була побудована неправильно. У будь-якому випадку рішення не змушує GC ...


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