Вбудований C ++: використовувати STL чи ні?


74

Я завжди був вбудованим програмним інженером, але зазвичай на рівні 3 або 2 стеку OSI. Я насправді не апаратний хлопець. Як правило, я завжди робив телекомунікаційні продукти, як правило, ручні / стільникові телефони, що, як правило, означає щось на зразок процесора ARM 7.

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

Я читав досить багато про дебати щодо використання STL у C ++ у вбудованих системах, і однозначної відповіді немає. Є кілька невеликих проблем з приводу переносимості, і деякі з приводу розміру коду або часу роботи, але у мене є дві основні проблеми:
1 - обробка винятків; Я досі не впевнений, чи використовувати його (див. Вбудований С ++: використовувати винятки чи ні? )
2 - Мені дуже не подобається динамічне розподіл пам'яті у вбудованих системах через проблеми, які він може створити. Зазвичай у мене є пул буферів, який статично виділяється під час компіляції і який обслуговує лише буфери фіксованого розміру (якщо буфер відсутній, скидання системи). STL, звичайно, робить багато динамічного розподілу.

Тепер я повинен прийняти рішення, використовувати чи відмовитись від STL - для всієї компанії назавжди (це входить до деяких основних s / w).

У який бік мені стрибати? Супер безпечний і втрачає більшу частину того, що становить C ++ (imo, це більше, ніж просто визначення мови), і можливо, пізніше натрапляє на проблеми або доводиться додавати багато обробки винятків і, можливо, якогось іншого коду зараз?

У мене є спокуса просто скористатися Boost , але 1) я не впевнений, чи буде він перенесений на кожен вбудований процесор, який я, можливо, захочу використовувати, і 2) на своєму веб-сайті вони кажуть, що не гарантують / рекомендують певні його частини. для вбудованих систем (особливо FSM, що здається дивним). Якщо я піду на Boost і пізніше ми знайдемо проблему ....


8
STL є частиною мови C ++. Якщо вас турбує пам'ять, замініть оператор new та delete на власне управління пам'яттю.
GManNickG

6
Ви перевіряли uSTL? ustl.sourceforge.net
Мануель

1
Виглядає добре, дякую. Звичайно, мені потрібно глибше заглибитися у портативність.
Моуг каже, відновити Моніку

3
Більшість контейнерів C ++ приймають об'єкт "розподілювач", який повідомляє, де взяти динамічну пам'ять. Ви можете повністю контролювати пам’ять, досить легко. (не все вимагає розподільників, але більшість речей це робить)
Мукінг Качка

1
Погляньте на слайди Мейєра про використання C ++ для вбудованих: htrd.su/wiki/_media/zhurnal/2013/03/28/…
Клаудіо

Відповіді:


35

Супер безпечний і втрачає більшу частину того, що становить C ++ (imo, це більше, ніж просто визначення мови), і можливо, пізніше натрапляє на проблеми або доводиться додавати багато обробки винятків і, можливо, якогось іншого коду зараз?

Ми маємо подібні дебати в ігровому світі, і люди спускаються з обох сторін. Щодо цитованої частини, чому б ви були стурбовані втратою "значної частини того, що становить C ++"? Якщо це не прагматично, не використовуйте його. Не має значення, це "С ++" чи ні.

Запустіть кілька тестів. Чи можете ви обійти управління пам’яттю STL способами, які вас задовольняють? Якщо так, то чи варто було докладати зусиль? Багато проблем STL та boost розроблені для вирішення, просто не виникають, якщо ви проектуєте, щоб уникнути випадкового динамічного розподілу пам'яті ... чи вирішує STL конкретну проблему, з якою стикаєтесь?

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


1
Дякую, Ден, це та інші (також проголосували) змусили мене насправді задуматися. Оскільки у нас є вбудована система, у нас є власний пул пам'яті. STL корисний нам переважно для класів контейнерів; але ми максимумуємо їх під час ініціалізації. Отже, або ми живемо з цим, і правило не передбачає розподілу STL після запуску системи, або ми можемо просто використовувати звичайні старі масиви (покажчиків на статично розподілені об'єкти)
Моуг каже, відновити Моніку

47

Я працюю над вбудованими системами в режимі реального часу щодня. Звичайно, моє визначення вбудованої системи може відрізнятися від вашого. Але ми в повній мірі використовуємо STL та винятки і не зазнаємо жодних некерованих проблем. Ми також використовуємо динамічну пам'ять (з дуже високою швидкістю; виділення великої кількості пакетів в секунду тощо) і поки що нам не потрібно вдаватися до будь-яких спеціальних розподільників або пулів пам'яті. Ми навіть використовували C ++ в обробниках переривань. Ми не використовуємо підсилення, але лише тому, що певна державна установа не дозволяє нам.

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

Редагувати: Отримавши підтримку за це через 2 роки, дозвольте мені опублікувати оновлення. Ми розвиваємось набагато далі, і нарешті ми потрапили в наш код, де стандартні бібліотечні контейнери занадто повільні в умовах високої продуктивності. Тут ми фактично вдалися до власних алгоритмів, пулів пам'яті та спрощених контейнерів. У цьому і полягає принадність C ++, однак ви можете скористатися стандартною бібліотекою та отримати все хороше, що вона надає, на 90% випадків використання. Ви не викидаєте все це, коли стикаєтеся з проблемами, ви просто оптимізуєте проблемні місця вручну.


3
+1, корисна відповідь. Але я не думаю, що ви знаєте так багато про винятки чи роздуття коду, як ви думаєте - знайомтеся з моїми коментарями у відповідь на ваші публікації інших людей.
j_random_hacker

1
Де саме у моїй відповіді з'являється фраза "роздуття коду"? Я вдячний +1, але, будь ласка, скеруйте свої коментарі саме на цю відповідь.
Брайан Ніл

звучить чудово (і так, обидві ці книги (і ціла "ефективна ..."
Мейєрса

Ви вже можете використовувати Boost?
SS Anne

25

В інших публікаціях розглядалися важливі питання динамічного розподілу пам'яті, винятки та можливе роздуття коду. Я просто хочу додати: Не забувайте про <algorithm>! Незалежно від того, чи використовуєте ви вектори STL або прості масиви C і покажчики, ви можете використовувати sort(), binary_search(), random_shuffle(), функції для створення і управління купами і т.д. Ці процедури майже напевно будуть швидше і менше помилок , ніж версії ви будуєте сам.

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


19

Пол Педріана з Electronic Arts написав у 2007 році тривалий трактат про те, чому STL не підходить для розробки вбудованих консолей і чому їм доводиться писати власні. Це докладна стаття, але найважливішими причинами були:

  1. Розподільники STL повільні, роздуті та неефективні
  2. Насправді компілятори не дуже добре вбудовують усі ці глибокі виклики функцій
  3. Розподільники STL не підтримують явне вирівнювання
  4. Алгоритми STL, що постачаються з GCC та STV MSVC, не надто ефективні, оскільки вони дуже агностичні на платформі і, отже, пропускають багато мікрооптимізацій, які можуть мати велике значення.

Кілька років тому наша компанія прийняла рішення взагалі не використовувати STL, замість цього впровадивши власну систему контейнерів, яка є максимально ефективною, простішою для налагодження та більш консервативною пам'яттю. Це була велика робота, але вона багаторазово окупилася. Але наш - це простір, в якому продукти змагаються за те, скільки вони можуть набити 16,6 мс при заданому обсязі процесора та пам'яті.

Щодо винятків: вони працюють повільно на консолях, і кожен, хто скаже вам інше, не пробував встановлювати час. Просто компіляція з увімкненими програмами сповільнить роботу всієї програми через необхідний код пролога / епілога - виміряйте самі, якщо не вірите мені. Це навіть гірше на замовлених процесорах, ніж на x86. З цієї причини компілятор, який ми використовуємо, навіть не підтримує винятки на C ++.

Приріст продуктивності полягає не стільки в тому, щоб уникнути вартості викидів, скільки в тому, щоб повністю вимкнути винятки.


12
Ви зв’язали статтю 2006 року, яка зараз застаріла. Винятки на C ++ не є повільними для пристойних сучасних компіляторів. Якщо ви маєте справу із вбудованою системою, для якої не існує гідного сучасного копімілера, у вас є проблема - але давати ковдру "Щодо винятків: вони повільні", це абсолютно неправильно.
JoeG

15
Визнані експерти C ++, такі як Герб Саттер та Андрій Александреску, не згодні з вашим твердженням "винятки повільні". Якщо ви не використовуєте винятки, ви самі тепер відповідаєте за написання та перевірку кодів повернення помилок, і цей код майже завжди менш ефективний і порівняно з кодом, який сучасні компілятори видають для винятків. Крім того, код, який люди пишуть (якщо вони взагалі намагаються його написати), щоб перевірити коди помилок, часто багатий на помилки та помилки.
Брайан Ніл

4
Винятки не дуже повільні, але вони накладають ненульові накладні витрати на принаймні один популярний сучасний компілятор (MSVC ++ 9), навіть коли жоден виняток не виникає. Щоб побачити це, спробуйте скомпілювати (не пов'язуючи) pastebin.com/m1fb29a45 з, /EHaа потім за /EHscдопомогою / Fa, щоб створити список складання. В обох випадках запроваджено управління структурованою обробкою винятків Win32 (SEH) - це додаткове переміщення даних у стек та налаштування FSрегістру сегментів.
j_random_hacker

10
Стаття з 2006 року, але мої власні терміни були з серпня 2009 року. Я прочитав всю теорію про те, як винятки вже не є повільними, але вони не підтверджують фактичні вимірювання, які я зробив .
Crashworks

2
Брайан: це пункти Е.А., не мої, але №4 було визначено емпірично. В основному, вони написали власні реалізації контейнерів і виявили, що вони працюють набагато швидше, ніж STL. Тому STL не є максимально ефективним.
Crashworks

15

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

Шаблони, що використовуються STL, ніколи не збираються генерувати код, який вам не потрібно було б створювати самому, тому я б не турбувався про роздуття коду.

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

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


1
+1, я вважаю, що це один з небагатьох випадків, коли гарну ідею перенести будівельні роботи з конструкцій.
j_random_hacker

6
Що ви маєте на увазі "STL не викидає винятки самостійно"? Що робити, якщо ви викликаєте vector :: at з індексом поза діапазоном? І ви також можете налаштувати потоки вводу-виводу для видалення винятків. Крім того, шаблони можуть генерувати більше коду, ніж ви можете, якщо ви писали його від руки. Див. Приклад у Stroustrup про поєднання шаблону з void * для зменшення такого здуття.
Брайан Ніл

4
@Brian: vector::at()є гарним прикладом. Було б точніше сказати, що STL можна використовувати таким чином, що він ніколи не генеруватиме винятків (тут, використовуючи operator[]()замість at()) і не роблячи жодних додаткових компромісів.
j_random_hacker

@Brian: Щодо здуття коду, функції, що містять ідентичний об'єктний код, будуть видалені під час зв'язку з MSVC ++, якщо ви вказали / Gy для компілятора та / OPT: ICF для компонувальника. Я вважаю, що компонувальник GNU може зробити те ж саме.
j_random_hacker

1
@ Брайан Ніл, я забув про це vector::at, і, мабуть, ще декілька інших - дякую за роз'яснення. Має бути можливість шукати у стандартних файлах бібліотеки „кидок” і знаходити всі „винятки” з мого надто узагальненого твердження.
Марк Ренсом

8

Проект з відкритим кодом "Вбудована бібліотека шаблонів (ETL)" націлений на звичайні проблеми зі STL, що використовуються у вбудованих програмах, шляхом надання / реалізації бібліотеки:

  • детермінована поведінка
  • "Створіть набір контейнерів, де розмір або максимальний розмір визначається під час компіляції. Ці контейнери повинні бути значною мірою еквівалентними тим, що надаються в STL, із сумісним API."
  • відсутність динамічного розподілу пам'яті
  • RTTI не потрібен
  • мало використання віртуальних функцій (лише за крайньої необхідності)
  • набір контейнерів з фіксованою місткістю
  • кешувати кеш-пам’ять контейнерів як постійно виділений блок пам’яті
  • зменшений розмір коду контейнера
  • безпечні розумні переліки
  • Розрахунки CRC
  • контрольна сума та хеш-функції
  • Варіанти = сорти безпечних об'єднань типу
  • Вибір тверджень, винятків, обробник помилок або відсутність перевірок на помилки
  • сильно перевірений агрегат
  • добре задокументований вихідний код
  • та інші функції ...

Ви також можете розглянути комерційний ST ++ для вбудованих розробників на C ++, наданий ESR Labs.


5
  1. для управління пам’яттю ви можете реалізувати власний розподільник, який запитує пам’ять із пулу. І всі контейнери STL мають шаблон для розподілювача.

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

  3. отже, я думаю, ви можете використовувати STL у вбудованій системі :)


4

На додаток до всіх коментарів, я пропоную вам прочитати Технічний звіт щодо продуктивності С ++, який конкретно стосується тем, які вас цікавлять: використання С ++ у вбудованих (включаючи жорсткі системи реального часу); як зазвичай застосовується обробка винятків та які накладні витрати вона має; накладні витрати на розподіл безкоштовного магазину.

Звіт справді хороший, так як розвінчує багато популярних хвостів про продуктивність С ++.


3

В основному це залежить від вашого компілятора та від обсягу вашої пам'яті. Якщо у вас більше декількох Кб оперативної пам'яті, динамічне розподіл пам'яті дуже допомагає. Якщо реалізація malloc зі стандартної бібліотеки, яка у вас є, не налаштована на обсяг вашої пам'яті, ви можете написати свою власну, або навколо є гарні приклади, такі як mm_malloc від Ральфа Хемпеля, які ви можете використовувати для написання нових та видалення операторів зверху .

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

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

Наприклад, якщо у вас є великий буфер у векторі, в якийсь момент він може виконати перерозподіл і закінчить, використовуючи в 1,5 рази більше обсягу пам'яті, який ви збираєтеся використовувати в якийсь момент під час перерозподілу та переміщення даних. (Наприклад, в якийсь момент йому виділено N байтів, ви додаєте дані через додаток або ітератор вставки, і він виділяє 2N байтів, копіює перший N і випускає N. У вас є 3N байтів, виділених у певний момент).

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

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

Виняток із ARM realview 3.1:

--- OSD\#1504 throw fapi_error("OSDHANDLER_BitBlitFill",res);
   S:218E72F0 E1A00000  MOV      r0,r0
   S:218E72F4 E58D0004  STR      r0,[sp,#4]
   S:218E72F8 E1A02000  MOV      r2,r0
   S:218E72FC E24F109C  ADR      r1,{pc}-0x94 ; 0x218e7268
   S:218E7300 E28D0010  ADD      r0,sp,#0x10
   S:218E7304 FA0621E3  BLX      _ZNSsC1EPKcRKSaIcE       <0x21a6fa98>
   S:218E7308 E1A0B000  MOV      r11,r0
   S:218E730C E1A0200A  MOV      r2,r10
   S:218E7310 E1A01000  MOV      r1,r0
   S:218E7314 E28D0014  ADD      r0,sp,#0x14
   S:218E7318 EB05C35F  BL       fapi_error::fapi_error   <0x21a5809c>
   S:218E731C E3A00008  MOV      r0,#8
   S:218E7320 FA056C58  BLX      __cxa_allocate_exception <0x21a42488>
   S:218E7324 E58D0008  STR      r0,[sp,#8]
   S:218E7328 E28D1014  ADD      r1,sp,#0x14
   S:218E732C EB05C340  BL       _ZN10fapi_errorC1ERKS_   <0x21a58034>
   S:218E7330 E58D0008  STR      r0,[sp,#8]
   S:218E7334 E28D0014  ADD      r0,sp,#0x14
   S:218E7338 EB05C36E  BL       _ZN10fapi_errorD1Ev      <0x21a580f8>
   S:218E733C E51F2F98  LDR      r2,0x218e63ac            <OSD\#1126>
   S:218E7340 E51F1F98  LDR      r1,0x218e63b0            <OSD\#1126>
   S:218E7344 E59D0008  LDR      r0,[sp,#8]
   S:218E7348 FB056D05  BLX      __cxa_throw              <0x21a42766>

Це не здається настільки страшним, і всередині блоків або функцій {} не додаються накладні витрати, якщо виняток не створено.


1

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

Я б серйозно дослідив створення власного управління пам’яттю, побудованого шляхом заміни операторів new / delete. Я майже впевнений, що трохи часу це можна зробити, і це майже напевно того варте.

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

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

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

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


6
-1 за повну відсутність розуміння того, як реалізуються винятки.
Billy ONeal

3
Винятки, реалізовані сучасними компіляторами, як правило, не спричиняють накладні витрати часу, якщо насправді не виникає виняток. І якщо ви будете обережно використовувати винятки (а не для звичайного контролю потоку), продуктивність не буде проблемою, коли справа піде не так.
Брайан Ніл

3
Ти приурочив це, Брайане? Востаннє, коли я намагався його виміряти (минулого літа), я виявив, що просто ввімкнення винятків та розгортання стеку в налаштуваннях компілятора спричинило уповільнення, незалежно від того, кидав я якісь винятки чи ні.
Crashworks

2
@Brian: Принаймні на Win32 кожен tryблок повинен встановити EXCEPTION_REGISTRATIONблок у стеку і спрямовувати на нього регістр FS. Це відбувається незалежно від того, чи насправді трапляються якісь винятки. Джерело: microsoft.com/msj/0197/exception/exception.aspx Також компілятор повинен додати код до кожного блоку, який оголошує будь-які об'єкти з нетривіальними деструкторами, якщо він не може довести, що виняток не може відбутися всередині блоку. В іншому випадку, як ці об’єкти будуть знищені під час розмотування стека?
j_random_hacker

3
@Brian: Цікаво, що я щойно спробував варіацію мого фрагмента pastebin на Linux x86 g ++ 4.2.1, і на його честь, єдина різниця полягала в додаткових 32 байтах, виділених у стеку, але не записаних в. Отже, здається, що у функції, якщо є якісь локальні змінні, які не вміщуються в регістри (тобто в стеці все одно має бути виділено простір), додаткові вказівки не виконуватимуться, якщо не буде вилучено чи викинуто жодних винятків . Дуже вражає!
j_random_hacker

1

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


+1 хороший бал. Процесори у "вбудованих" проектах стають все потужнішими. Мій поточний процесор Atmel - це серія UC3, яка є 32-бітною. Коли я починав, вбудований означав 4 або 8 біт. Однак 32-розрядний процесор має лише 512 кБ пам’яті користувача, що робить речі дещо жорсткими. У вас немає проблем з пам’яттю?
Моуг каже, щоб відновити Моніку
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.