Фізичні витоки
Помилки, з якими GC звертається (принаймні, до зовнішнього спостерігача), те, що програміст, який добре знає свою мову, бібліотеки, поняття, ідіоми тощо, не робив. Але я можу помилитися: чи ручне управління пам’яттю суттєво складне?
Виходячи з кінця С, що робить управління пам'яттю приблизно максимально ручним і яскраво вираженим, щоб ми порівнювали крайнощі (C ++ здебільшого автоматизує управління пам’яттю без GC), я б сказав «не дуже» в сенсі порівняння з GC, коли він приходить до витоків . Новачок, а іноді навіть і професіонал, може забути написати free
для даної задачі malloc
. Це однозначно буває.
Однак є такі інструменти, як valgrind
виявлення витоку, які негайно помітять при виконанні коду, коли / де такі помилки трапляються до точного рядка коду. Коли це інтегровано в CI, злиття таких помилок стає майже неможливим, і їх легко виправити, як пиріг. Тож ніколи не буває великої справи в будь-якій команді / процесі з розумними стандартами.
Зрозуміло, можуть бути якісь екзотичні випадки страти, які летять під радаром тестування, де free
не вдалося викликати, можливо, наткнувшись на неясну зовнішню помилку введення, наприклад, пошкоджений файл, і в цьому випадку, можливо, система просочується 32 байти чи щось. Я думаю, що це однозначно може статися навіть при досить хороших стандартах тестування та інструментах виявлення витоку, але також було б не так вже й критично, щоб витікати трохи пам'яті на щось, що майже ніколи не відбувається. Ми побачимо набагато більшу проблему, де ми можемо просочувати масивні ресурси навіть у загальних шляхах виконання нижче таким чином, що GC не може запобігти.
Це також важко, якщо щось не нагадує псевдо-форму GC (підрахунок посилань, наприклад), коли термін експлуатації об'єкта потрібно продовжити для певної форми відкладеної / асинхронної обробки, можливо, іншим потоком.
Показні покажчики
Справжня проблема з більш ручними формами управління пам’яттю не протікає для мене. Скільки власних додатків, написаних на C або C ++, ми знаємо про це, що насправді є прохідними? Чи протікає ядро Linux? MySQL? CryEngine 3? Цифрові аудіо робочі станції та синтезатори? Чи протікає Java VM (вона реалізована в рідному коді)? Photoshop?
Якщо що, я думаю, коли ми оглядаємось, найпростішими програмами, як правило, є ті, які написані за допомогою схем GC. Але перш ніж це сприйняти як шланг по збору сміття, в кодовому коді є значна проблема, яка зовсім не пов'язана з витоком пам'яті.
Питанням для мене завжди була безпека. Навіть коли ми free
пам’ятаємо через покажчик, якщо є якісь інші вказівники на ресурс, вони стануть звисаючими (недійсними) покажчиками.
Коли ми намагаємося отримати доступ до показів цих звисаючих покажчиків, ми стикаємося з невизначеною поведінкою, хоча майже завжди є порушенням сегментації / доступу, що призводить до жорсткої, негайної аварії.
У всіх тих перелічених вище додатків, які я перераховував вище, потенційно є незрозумілий край або два випадки, які можуть призвести до аварії в першу чергу через цю проблему, і, безумовно, є велика частка сором'язливих додатків, написаних у власному коді, які дуже важкі, і часто значною мірою через це питання.
... і це тому, що управління ресурсами важке, незалежно від того, використовуєте ви GC чи ні. Практична відмінність часто є або протіканням (GC), або збоєм (без GC) на тлі помилки, що призводить до керування ресурсами.
Управління ресурсами: Збір сміття
Комплексне управління ресурсами - це нелегкий, ручний процес, незалежно від того. GC тут не може нічого автоматизувати.
Візьмемо приклад, де у нас є цей об’єкт, "Джо". На Джо посилається ряд організацій, членом яких він є. Щомісяця або близько того вони витягують членський внесок з його кредитної картки.
У нас також є одне посилання на Джо, щоб контролювати його життя. Скажімо, нам, як програмістам, Джо більше не потрібен. Він починає нас докучати, і нам більше не потрібні ці організації, яким він належить витрачати свій час на боротьбу з ним. Тож ми намагаємось витерти його з лиця землі, видаливши його життєву орієнтир.
... але зачекайте, ми використовуємо збирання сміття. Кожна сильна згадка про Джо буде тримати його навколо. Тож ми також видаляємо посилання на нього з організацій, до яких він належить (відмовившись від нього).
... окрім ну, ми забули скасувати підписку на його журнал! Тепер Джо залишається навколо в пам’яті, домагаючись нас і використовуючи ресурси, а журнальна компанія також закінчує продовжувати обробляти членство Джо щомісяця.
Це основна помилка, яка може спричинити протікання і початок використання все більшої кількості пам'яті, що довше вони працюють, і, можливо, все більше й більше обробляється (що повторюється підписка на журнал). Вони забули видалити одну чи декілька з цих посилань, унеможлививши, що сміттєзбірник не робить свою магію, поки вся програма не буде закрита.
Однак програма не завершується збоєм. Це абсолютно безпечно. Це просто збереже придушення пам’яті, і Джо все ще буде затримуватися. Для багатьох застосувань подібне витікаюче поведінка, де ми просто кидаємо все більше і більше пам’яті / обробки на проблему, може бути набагато кращим для важкої аварії, особливо враховуючи, скільки пам'яті та потужності процесора мають наші машини сьогодні.
Управління ресурсами: Посібник
Тепер розглянемо альтернативу, де ми використовуємо вказівники на Джо та ручне управління пам’яттю, наприклад:
Ці сині посилання не керують життям Джо. Якщо ми хочемо видалити його з лиця землі, ми вручну просимо його знищити так:
Тепер це нормально залишатиме нам вказівники повсюди, тож давайте видалимо покажчики Джо.
... ого, ми знову зробили таку саму помилку і забули скасувати підписку на журнал Джо на підписку!
За винятком тепер у нас є звисаючий покажчик. Коли підписка на журнал намагається обробити щомісячну плату Джо, весь світ вибухне - зазвичай ми отримуємо важкий збій миттєво.
Ця сама основна помилка в управлінні ресурсом, коли розробник забув видалити всі вказівники / посилання на ресурс, може призвести до безлічі збоїв у нативних програмах. Вони не завищують пам’ять, чим довше вони працюють, тому що в такому випадку вони часто відвертаються.
Реальний світ
Тепер у наведеному прикладі використовується смішно проста діаграма. У додатку в реальному світі можуть знадобитися тисячі зображень, скріплених разом, щоб покрити повний графік, із сотнями різних типів ресурсів, що зберігаються в графіку сцени, ресурси GPU, пов’язані з деякими з них, прискорювачі, прив’язані до інших, спостерігачі, розподілені по сотнях плагінів спостерігаючи за різними типами об'єктів на сцені за змінами, спостерігачами спостерігають за спостерігачами, аудіо, синхронізованими з анімацією тощо. Тому може здатися, що легко уникнути помилки, яку я описав вище, але це, як правило, ніде не так просто в реальному світі виробнича кодова база для складної програми, що охоплює мільйони рядків коду.
Шанс, що хтось колись буде керувати ресурсами десь у цій кодовій базі, як правило, досить високий, і ця ймовірність збігається з GC або без нього. Основна відмінність полягає в тому, що станеться в результаті цієї помилки, що також впливає на те, як швидко ця помилка буде виявлена та виправлена.
Краш проти витоку
Тепер який із них гірший? Негайний крах або мовчазна пам'ять просочилася, де Джо просто загадково затримується?
Більшість може відповісти на останнє, але скажімо, що це програмне забезпечення розроблено так, щоб він працював годинами, можливо, днями, і кожен з цих Джо і Джейн, який ми додаємо, збільшує використання пам'яті програмного забезпечення на гігабайт. Це не критичне програмне забезпечення (збої насправді не вбивають користувачів), а критичне для продуктивності.
У цьому випадку важкий збій, який одразу з’являється при налагодженні, вказуючи на допущену вами помилку, може насправді віддати перевагу просто протікає програмному забезпеченню, яке може летіти навіть під радаром вашої процедури тестування.
З іншого боку, якщо це найважливіше програмне забезпечення, де продуктивність не є ціллю, а не руйнується будь-якими можливими способами, то витік може бути фактично кращим.
Слабка література
Існує свого роду гібрид цих ідей, доступних у схемах GC, відомих як слабкі посилання. Маючи слабкі посилання, ми можемо мати всі ці організації слабким посиланням на Джо, але не перешкоджати його видаленню, коли чітке посилання (власник / життєвий шлях Джо) відходить. Тим не менш, ми отримуємо вигоду від того, що зможемо виявити, коли Джо вже не існує навколо цих слабких посилань, що дозволяє нам отримати легко відтворювану помилку.
На жаль, слабкі посилання використовуються не стільки, скільки вони, мабуть, повинні використовуватися, тому часто багато складних програм GC можуть бути чутливими до витоків, навіть якщо вони потенційно набагато менш аварійні, ніж складні програми C, наприклад
У будь-якому випадку, чи полегшує вам чи важче життя GC, залежить від того, наскільки важливо для Вашого програмного забезпечення уникати протікань і чи стосується він складного управління ресурсами такого роду чи ні.
У моєму випадку я працюю в критичному для продуктивності полі, де ресурси охоплюють сотні мегабайт до гігабайт, і не випускають цю пам'ять, коли користувачі просять вивантажити через помилку, як описано вище, насправді можуть бути менш бажаними для аварії. Збої легко помітити та відтворити, що робить їх найчастіше улюбленим видом помилки програміста, навіть якщо це найменш улюблений користувач, і багато цих збоїв відображатимуться з розумною процедурою тестування, перш ніж вони навіть дістануться до користувача.
У всякому разі, це відмінності між GC та ручним управлінням пам'яттю. Щоб відповісти на ваше безпосереднє запитання, я б сказав, що керування ручною пам’яттю є важким, але це має дуже мало спільного з витоками, і як GC, так і ручні форми управління пам'яттю все ще дуже складні, коли управління ресурсами нетривіальне. Можливо, GC має більш хитру поведінку, коли програма, здається, працює просто чудово, але витрачає все більше і більше ресурсів. Ручна форма є менш хитрою, але вона вийде з ладу і спалить великий час з помилками, як показана вище.