Чому розумні покажчики так популярні?


52

Як я бачу, розумні покажчики широко використовуються у багатьох реальних проектах C ++.

Хоча якісь розумні покажчики, очевидно, вигідні для підтримки RAII та передачі права власності, також існує тенденція використання спільних покажчиків за замовчуванням як способу "вивезення сміття" , так що програмісту не доведеться думати про те, щоб розподілити стільки .

Чому спільні покажчики популярніші, ніж інтеграція належного сміттєзбірника, як Boehm GC ? (Або ти взагалі згоден, що вони популярніші, ніж фактичні ГК?)

Я знаю про дві переваги звичайних ГК перед підрахунком довідок:

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

Які причини використання інтелектуальних покажчиків для підрахунку довідок?


6
Я просто додам зауваження, що це неправильне використання за замовчуванням: у більшості випадків std::unique_ptrдостатньо, і як таке має нульові накладні витрати над необробленими покажчиками з точки зору продуктивності роботи. Використовуючи std::shared_ptrвсюди, ви також затемнюєте семантику власності, втрачаючи одну з головних переваг розумних покажчиків, крім автоматичного управління ресурсами - чітке розуміння наміру коду.
Метт

2
Вибачте, але прийнята відповідь тут абсолютно неправильна. Підрахунок посилань має більш високі накладні витрати (підрахунок замість біта позначки та більш повільна продуктивність), необмежений час паузи, коли декременти лавинні, і не складніший, скажімо, напівпростір Чейні.
Джон Харроп

Відповіді:


57

Деякі переваги довідкового підрахунку щодо вивезення сміття:

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

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

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

  4. Стандартний. C ++ включає підрахунок посилань (через shared_ptr) та друзів у STL, це означає, що більшість програмістів на C ++ знайомі з ним, і більшість C ++ код буде працювати з ним. Однак немає жодного стандартного сміттєзбірника C ++, це означає, що вам потрібно вибрати його і сподіватися, що він добре працює для вашого випадку використання - і якщо це не так, це вирішити проблему, а не мову.

Щодо передбачуваних недоліків підрахунку еталонів - не виявлення циклів - це проблема, але така, з якою я ніколи особисто не стикався за останні десять років використання підрахунку еталонів. Більшість структур даних є природно ациклічними, і якщо ви стикаєтесь із ситуацією, коли вам потрібні циклічні посилання (наприклад, батьківський вказівник у дереві), ви можете просто використовувати слабкий_птр або необроблений вказівник С для "зворотного напрямку". Поки ви знаєте про потенційну проблему під час проектування своїх структур даних, це не проблема.

Щодо продуктивності, у мене ніколи не було проблем із виконанням довідкового підрахунку. У мене виникли проблеми з виконанням збору сміття, зокрема з випадковими заморозками, які може спричинити GC, і єдине рішення ("не виділяти об'єкти") може бути також перефразоване як "не використовувати GC" .


16
Наївні реалізації довідкових підрахунків зазвичай отримують набагато нижчу продуктивність, ніж виробничі ГК (30–40%) за рахунок затримки. Розрив можна усунути за допомогою оптимізацій, таких як використання меншої кількості біт для знижки, а також уникнення відстеження об'єктів до тих пір, поки вони не уникнуть. C ++ робить це останнім природним чином, якщо ви головним чином make_sharedпри поверненні. Тим не менш, затримка, як правило, є більшою проблемою в додатках у режимі реального часу, але пропускна здатність є більш важливою, як правило, саме тому відстеження ГК настільки широко використовується. Я б не так швидко говорив про них погано.
Джон Перді

3
Я б посперечався на «простіше»: простіше з точки зору загальної кількості необхідної реалізації, так, але не простіше для коду, який його використовує : порівняйте, розповідаючи комусь, як користуватися RC («зробіть це під час створення об’єктів, і це при їх знищенні» ) як (наївно, що досить часто) використовувати GC ('...').
AakashM

4
"Підраховуючи посилання, ви гарантуєте, що ваш об'єкт буде звільнений миттєво, коли остання посилання на нього піде". Це поширена помилка. flyingfrogblog.blogspot.co.uk/2013/10/…
Джон Харроп

4
@JonHarrop: Ця публікація в блозі жахливо неправильна. Ви також повинні прочитати всі коментарі, особливо останній.
Дедуплікатор

3
@JonHarrop: Так, є. Він не розуміє, що термін експлуатації - це повний обсяг, який підходить до заключної дужки. А оптимізація в F #, яка, згідно з коментарями, лише іноді працює, закінчує термін експлуатації раніше, якщо змінна не використовується знову. Що, природно, має свої небезпеки.
Дедуплікатор

26

Щоб отримати хороші показники роботи в GC, GC повинен мати можливість переміщати об'єкти в пам'яті. Такою мовою, як C ++, де ви можете безпосередньо взаємодіяти з місцями пам'яті, це майже неможливо. (Microsoft C ++ / CLR не враховується, оскільки він вводить новий синтаксис для покажчиків, керованих GC, і таким чином фактично є іншою мовою.)

Boehm GC, хоча витончена ідея, насправді є найгіршим з обох світів: вам потрібен malloc (), який повільніше, ніж хороший GC, і таким чином ви втрачаєте детерміновану поведінку розподілу / делокації без відповідного підвищення продуктивності покоління GC . Крім того, він необхідний консервативний, так що не обов’язково збирати все сміття.

Гарний, добре налаштований GC може бути чудовою справою. Але у такій мові, як C ++, виграші мінімальні, а витрати часто просто не варті.

Однак буде цікаво побачити, як C ++ 11 стає більш популярним, чи починають лямбди та семантику захоплення вести спільноту C ++ до тих самих проблем розподілу та життєвого циклу об'єктів, які спричинили спільноту Lisp винаходити GC в першу чергу місце.

Дивіться також мою відповідь на пов’язане питання на StackOverflow .


6
ПОПЕРЕДЖАЙТЕ Boehm GC, я час від часу замислювався про те, наскільки він особисто відповідає за традиційну неприязнь до GC серед програмістів C і C ++, просто надаючи погане перше враження про технологію взагалі.
Левшенко

@Leushenko Добре сказано. Справа в цьому питанні, коли Boehm gc називають "правильним" gc, ігноруючи той факт, що він повільно і практично гарантовано протікає. Я знайшов це питання, вивчаючи, чи реалізував хтось переривник циклу стилю python для shared_ptr, що звучить як вагома мета для реалізації c ++.
user4815162342

4

Як я бачу, розумні покажчики широко використовуються у багатьох реальних проектах C ++.

Правда, але, об'єктивно, переважна більшість кодів зараз написана сучасними мовами з відстеженням сміттєзбірників.

Хоча якісь розумні покажчики, очевидно, вигідні для підтримки RAII та передачі права власності, також існує тенденція використання спільних покажчиків за замовчуванням як способу "вивезення сміття", так що програмісту не потрібно думати про те, щоб розподілити стільки .

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

Чому спільні покажчики популярніші, ніж інтеграція належного сміттєзбірника, як Boehm GC? (Або ти взагалі згоден, що вони популярніші, ніж фактичні ГК?)

Ох, так багато речей не так з вашим мисленням:

  1. GC Boehm - це не "належний" GC в будь-якому сенсі цього слова. Це справді жахливо. Він консервативний, тому він протікає і неефективний по конструкції. Дивіться: http://flyingfrogblog.blogspot.co.uk/search/label/boehm

  2. Об'єктивно вказівники об'єктивно ніде не такі популярні, як GC, оскільки переважна більшість розробників зараз використовує мови GC'd і не мають потреби в загальних вказівниках. Подивіться на Java та Javascript на ринку праці порівняно з C ++.

  3. Ви, здається, обмежуєте розгляд C ++, оскільки, я припускаю, ви вважаєте, що GC - це дотична проблема. Це не ( єдиний спосіб отримати гідний GC - це спроектувати мову та VM для неї з самого початку), тому ви вводите упередженість вибору. Люди, які дуже хочуть належного збору сміття, не дотримуються C ++.

Які причини використання інтелектуальних покажчиків для підрахунку довідок?

Ви обмежені на C ++, але хочете, щоб у вас було автоматичне управління пам'яттю.


7
Гм, це питання з тегом c ++, яке говорить про функції C ++. Ясно, що будь-які загальні заяви говорять про в коді C ++, а в повному обсязі програмування. Таким чином, однак "об'єктивно" вивезення сміття може використовуватися за межами світу C ++, що в кінцевому рахунку не має жодного значення для розглянутого питання.
Нікол Болас

2
Ваш останній рядок явно помиляється: ви перебуваєте в C ++ і раді, що ви не змушені мати справу з GC, і це затягує звільнення ресурсів. Причина, що Apple не любить GC, і найголовніше керівництво для мов GC'd: Не створюйте сміття, якщо у вас не буває безресурсних ресурсів або ви не можете їм допомогти.
Дедуплікатор

3
@JonHarrop: Отже, порівнюйте еквівалентні невеликі програми з та без GC, які явно не вибрані для гри на користь обох сторін. На який, на вашу думку, потрібно більше пам’яті?
Дедуплікатор

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

3
@JonHarrop: Я мав на увазі з GC (відстеження чи що завгодно) та RAII, якщо ви говорите на C ++. Що включає підрахунок довідок, але лише там, де це корисно. Або ви могли порівняти з програмою Swift.
Дедуплікатор

3

У MacOS X та iOS, а також у розробників, які використовують Objective-C або Swift, підрахунок посилань популярний, оскільки він обробляється автоматично, а використання сміття значно зменшилось, оскільки Apple більше не підтримує його (мені кажуть, що програми використовують збирання сміття буде порушено в наступній версії MacOS X, а збір сміття ніколи не реалізовувався в iOS). Я насправді серйозно сумніваюся, що коли-небудь було багато програмного забезпечення, яке використовувало збирання сміття, коли воно було доступне.

Причина позбавлення від збирання сміття: він ніколи не працював надійно в середовищі стилю С, де вказівники могли «втекти» на місця, недоступні сміттєзбірникам. Apple твердо вірить і вважає, що підрахунок посилань відбувається швидше. (Ви можете робити будь-які претензії щодо відносної швидкості, але Apple ніхто не зміг переконати). І врешті-решт, ніхто не використовував збирання сміття.

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


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

Налагодження, чому сміттєзбірник знищив предмет, який я досі використовував (що призвело до аварії), вирішив це для мене :-)
gnasher729

О так, і це теж зробить. Ви зрештою з’ясували, чому?
Дедуплікатор

Так, це була одна з багатьох функцій Unix, де ви передаєте пустоту * як "контекст", який потім вам повертається у функції зворотного виклику; пустота * насправді був об’єктом Objective-C, і збирач сміття не усвідомлював, що об'єкт заховано у виклику Unix. Зворотний виклик викликається, відміняє недійсність * на Object *, kaboom!
gnasher729

2

Найбільший недолік збирання сміття в C ++ полягає в тому, що це неможливо отримати правильно:

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

    Наслідок: Будь-який збирач сміття C ++ буде просочувати предмети, які слід збирати.

  • У C ++ ви можете робити арифметику вказівника для отримання покажчиків. Таким чином, якщо ви не знайдете вказівник на початок блоку, це не означає, що на цей блок не можна посилатися.

    Наслідок: Будь-який збирач сміття C ++ повинен враховувати ці коригування, трактуючи будь-яку послідовність бітів, яка трапляється в будь-якій точці блоку, включаючи відразу після закінчення, як дійсний покажчик, на який посилається блок.

    Примітка: жоден C ++ збирач сміття не може обробляти код такими хитрощами:

    int* array = new int[7];
    array--;    //undefined behavior, but people may be tempted anyway...
    for(int i = 1; i <= 7; i++) array[i] = i;
    

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


2
" вони змішуються з іншими даними ". Це не стільки, скільки вони "змішані" з іншими даними. Використовувати систему типу C ++ легко, щоб побачити, що таке вказівник, а що ні. Проблема полягає в тому, що вказівники часто стають іншими даними. Сховати вказівник у ціле число - це, на жаль, поширений інструмент для багатьох API стилів C.
Нікол Болас

1
Вам навіть не потрібна невизначена поведінка, щоб викручувати сміттєзбірник в c ++. Наприклад, ви можете серіалізувати покажчик на файл і прочитати його згодом. Тим часом, ваш процес може не містити цього вказівника де-небудь у його адресному просторі, тож збирач сміття міг збирати цей об’єкт, а потім, коли ви дезаріалізуєте вказівник ... Уопс.
Bwmat

@Bwmat "Навіть"? Введення покажчиків у такий файл здається трохи ... надуманим. У будь-якому випадку, ті ж серйозні проблеми, що наштовхуються на стек об'єктів, вони можуть бути зникли, коли ви читаєте вказівник назад з файлу в іншому місці коду! Десеріалізація недійсного значення вказівника - це невизначена поведінка, не робіть цього.
гайда

Якщо звичайно, вам потрібно бути обережними, якщо ви робите щось подібне. Малося на увазі приклад того, що, як правило, сміттєзбірник не може працювати «належним чином» у всіх випадках у c ++ (без зміни мови)
Bwmat

1
@ gnasher729: Е-е, ні? Покажчики минулого кінця ідеально?
Дедуплікатор
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.