Правильно розміщувати об’єкти після завершення роботи сервера


9

Я працюю над великим проектом C ++. Він складається з сервера, який відкриває API REST, надаючи простий та зручний інтерфейс для дуже широкої системи, що включає безліч інших серверів. Кодова база досить велика і складна, і розвивалася в часі без належного дизайну наперед. Моє завдання - реалізувати нові функції та рефактор / виправити старий код, щоб зробити його більш стабільним та надійним.

На даний момент сервер створює ряд довгоживучих об'єктів, які ніколи не припиняються і не розміщуються, коли процес закінчується. Це робить Valgrind майже непридатним для виявлення витоків, оскільки неможливо відрізнити тисячі (сумнівно) законних витоків від «небезпечних».

Моя ідея полягає у тому, щоб усі об’єкти були розміщені до закінчення, але коли я зробив цю пропозицію, мої колеги та мій начальник виступили проти мене, зазначивши, що ОС все одно звільнить цю пам'ять (що очевидно для всіх) та розпоряджається об'єктами сповільнить відключення сервера (що, на даний момент, в основному є викликом до std::exit). Я відповів, що наявність "чистої" процедури відключення не обов'язково означає, що її потрібно використовувати. Ми завжди можемо зателефонувати std::quick_exitабо просто kill -9зробити процес, якщо відчуваємо нетерплячість.

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

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


Окрім аргументу продуктивності (що розумно!), Чи багато зусиль потрібно ізолювати довгоживучими об’єктами та додати для них код очищення?
Doc Brown

Відповіді:


7

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

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

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


1
+1 Прагматизм FTW. Є значення в вимірюванні, але є значення і в швидкому відключенні.
Росс Паттерсон

2
В якості альтернативи комутатору командного рядка ви також можете розглянути можливість видалення постійних об'єктів всередині блоку #ifdef DEBUG.
Жуль

3

Тут головне:

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

Це в значній мірі безпосередньо означає, що ваша кодова база зв'язана не тільки з надією та ниткою. У грамотних програмістів C ++ немає подвійних фрейлів.

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

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


Безумовно, проблема полягає в більшій картині. Однак дуже рідко можна знайти ресурси та дозвіл перефактурувати / переписати проект для кращої форми.
Cengiz Може

@CengizCan: Якщо ви хочете виправити помилки, вам потрібно зробити рефактор. Ось як це працює.
DeadMG

2

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

Приклади:

  • Довгоживучі об’єкти, на які ніхто не посилається (реальні витоки). Це логічна помилка програмування. Виправте ті, хто має нижчий пріоритет, НЕ БЕЗПЕЧНО, вони несуть відповідальність за збільшення вашої пам’яті з часом (і погіршення якості додатків) Якщо вони з часом зростуть у вашій пам'яті, виправте їх із більшим пріоритетом.

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

  • Довгоживучі об’єкти "задумом". Наприклад, синглтон. Їх справді важко позбутися, особливо якщо це багатопотокова програма.

  • Перероблені об'єкти. Довго живі предмети не завжди повинні бути поганими. Вони також можуть бути корисними. Замість того, щоб виділяти / переносити пам'ять з високою частотою, додавання в даний час невикористаних об'єктів до контейнера, з яких можна черпати, коли такий блок пам'яті потрібен знову, може допомогти пришвидшити додаток та уникнути фрагментації купи. Їх слід легко звільнити під час вимкнення, можливо, у спеціальній «приладовій / перевіреній» збірці.

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

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


0

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

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

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

http://en.wikipedia.org/wiki/Active_record_pattern

У C ++ я мав би, щоб кожен активний запис мав слабкий_ptr до сервера, який міг би кидати речі розумно, якщо підключення до сервера затьмариться. Ці класи можуть бути лінивими або пакетно заселеними, залежно від ваших потреб, але термін експлуатації цих об'єктів повинен бути лише там, де вони використовуються.

Дивись також:

Чи марно витрачати час на звільнення ресурсів, перш ніж я вийду з процесу?

Інші


This reeks of global variablesЯк ви переходите від "є тисячі об'єктів, які потрібно звільнити" до "вони повинні бути глобальними"? Це досить стрибок логіки.
Довал

0

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

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

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