Встроенный C++: использовать STL или нет?


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

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

Я читал совсем немного о дебатах об использовании STL в C++ во встроенных системах и нет четкого ответа. Есть некоторые небольшие проблемы с переносимостью, и несколько о размере кода или времени выполнения, но у меня есть две основные проблемы:
1-обработка исключений; я все еще не уверен, следует ли его использовать (см. встроенный C++ : использовать исключения или нет?)
2-я сильно не люблю динамическое выделение памяти во встроенных системах, из-за проблем, которые он может ввести. У меня вообще есть буферный пул, который статически выделяется во время компиляции и обслуживает только буферы фиксированного размера (если нет буферов, сброс системы). STL, конечно, делает много динамического распределения.

теперь я должен принять решение, использовать или отказаться от STL-для всей компании, навсегда (это происходит в некоторых очень основных s/w).

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

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

11 64

11 ответов:

супер-безопасная и потерять многое из того, что представляет собой C++ (ИМО, это больше, чем просто определение языка) и может быть, столкнуться с проблемами позже или есть чтобы добавить много обработки исключений & может какой другой код?

у нас есть подобные дебаты в игровом мире, и люди спускаются с обеих сторон. Что касается цитируемой части, почему вы беспокоитесь о потере "многого из того, что составляет C++"? Если это не прагматично, не используйте его. Это не должно иметь значения если это "C++" или нет.

выполнить некоторые тесты. Можете ли вы обойти управление памятью STL таким образом, чтобы удовлетворить вас? Если да, то стоило ли это усилий? Многие проблемы STL и boost предназначены для решения просто не придумывают, если вы разрабатываете, чтобы избежать случайного динамического выделения памяти... STL решает конкретную проблему, с которой вы сталкиваетесь?

многие люди решали STL в жестких условиях и были довольны этим. Многие люди просто избегают этого. Некоторые люди предлагают совершенно новые стандарты. Я не думаю, что есть один правильный ответ.

Я работаю на встроенных системах реального времени каждый день. Конечно, мое определение встроенной системы может отличаться от вашего. Но мы полностью используем STL и исключения и не испытываем никаких неуправляемых проблем. Мы также используем динамическую память (с очень высокой скоростью; выделение большого количества пакетов в секунду и т. д.) и еще не нужно прибегать к каким-либо пользовательским распределителям или пулам памяти. Мы даже использовали C++ в обработчиках прерываний. Мы не используем boost, но только потому, что определенный правительственное агентство нам не позволит.

Это наш опыт вы действительно можете использовать многие современные функции C++ во встроенной среде, пока вы используете свою голову и проводите свои собственные тесты. Я настоятельно рекомендую вам использовать Эффективный C++ 3-е издание, а также Саттер и Александреску Стандарты Кодирования C++, чтобы помочь вам в использовании C++ с вменяемым стиль программирования.

редактировать: после получения положительного голоса это спустя 2 года, позвольте мне опубликовать обновление. Мы значительно продвинулись в нашей разработке, и мы, наконец, попали в те места в нашем коде, где стандартные контейнеры библиотеки слишком медленны в условиях высокой производительности. Здесь мы фактически прибегли к пользовательским алгоритмам, пулам памяти и упрощенным контейнерам. Это красота C++, хотя вы можете использовать стандартную библиотеку и получить все хорошие вещи, которые она предоставляет для 90% ваших случаев использования. Вы не выбрасываете все это, когда сталкиваетесь с проблемами, вы просто оптимизируете вручную проблемные точки.

в других сообщениях были рассмотрены важные вопросы динамического выделения памяти, исключения и возможного раздувания кода. Я просто хочу добавить: не забывайте про <algorithm>! Независимо от того, используете ли вы векторы STL или простые массивы C и указатели, вы все равно можете использовать sort(),binary_search(),random_shuffle(), функции для строить и управлять кучи, etc. Эти процедуры почти наверняка будут быстрее и менее глючными, чем версии, которые вы создаете сами.

пример: если вы думаете о это тщательно, алгоритм перемешивания вы строите сами вероятно, будет производить искаженные распределения;random_shuffle() не будет.

Electronic Arts писал длинный трактат о том, почему STL не подходит для разработки встроенных консолей и почему они должны были писать свои собственные. Это подробная статья, но самые важные причины были:

  1. распределители STL медленные, раздутые, и неэффективно
  2. компиляторы на самом деле не очень хорошо встраивают все эти глубокие вызовы функций
  3. распределители STL не поддерживают явное выравнивание
  4. STL алгоритмы, которые поставляются с GCC и STL MSVC, не очень эффективны, потому что они очень агностичны для платформы и поэтому пропускают много микрооптимизаций, которые могут иметь большое значение.

несколько лет назад наша компания приняла решение вообще не использовать STL, вместо этого внедряя собственную систему контейнеров, которые максимально эффективны, легче отлаживаются и более консервативны в памяти. Это была большая работа, но она окупилась много раз. Но наш-это пространство внутри какие продукты конкурируют по тому, сколько они могут втиснуть в 16,6 МС с заданным размером процессора и памяти.

Что касается исключений:они медленно на консолях, и любой, кто говорит вам, иначе не пытался сроков их. Просто компиляция с их включенным замедлит всю программу из-за необходимого кода пролога/эпилога-измерьте его самостоятельно, если вы мне не верите. Это еще хуже на процессорах в порядке, чем на x86. По этой причине компилятор не даже поддержка исключений c++.

прирост производительности происходит не столько от того, чтобы избежать затрат на бросок исключения-это от полного отключения исключений.

позвольте мне начать с того, что я не делал встроенную работу в течение нескольких лет, и никогда в C++, поэтому мой совет стоит каждого пенни, который вы платите за него...

шаблоны, используемые STL, никогда не будут генерировать код, который вам не нужно будет генерировать самостоятельно, поэтому я бы не беспокоился о раздувании кода.

STL не бросает исключения сами по себе, так что это не должно быть проблемой. Если ваши классы не бросают, вы должны быть в безопасности. Разделите инициализацию объекта пусть конструктор создаст объект bare bones, а затем выполнит любую инициализацию, которая может привести к сбою в функции-члене, возвращающей код ошибки.

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

  1. для управления памятью вы можете реализовать свой собственный распределитель, который запрашивает память из пула. И все контейнеры STL имеют шаблон для распределителя.

  2. для исключения STL не вызывает много исключений, как правило, наиболее распространенными являются: из памяти, в вашем случае система должна сбросить, поэтому вы можете выполнить сброс в распределителе. другие, такие как вне диапазона, вы можете избежать его пользователем.

  3. Так, я думаю вы можете использовать STL во встроенной системе:)

это в основном зависит от вашего компилятора и в объеме памяти. Если у вас есть более нескольких Кб оперативной памяти, динамическое выделение памяти очень помогает. Если реализация malloc из стандартной библиотеки, которая у вас есть, не настроена на ваш размер памяти, вы можете написать свой собственный, или есть хорошие примеры, такие как mm_malloc от Ральфа Хемпеля что вы можете использовать для записи новых и удаления операторов сверху.

Я не согласен с тем, что повторите мем, что исключения и контейнеры stl слишком медленны или слишком раздуты и т. д. Конечно, он добавляет немного больше кода, чем простой c malloc, но разумное использование исключений может сделать код более понятным и избежать слишком большой ошибки проверки blurb в C.

нужно иметь в виду, что распределители STL увеличат свои распределения в степени двух, что означает, что иногда он будет делать некоторые перераспределения, пока не достигнет правильного размера, который вы можете предотвратить с помощью заповедник таким образом, он становится таким же дешевым, как один malloc нужного размера, если вы знаете размер, чтобы выделить в любом случае.

Если у вас есть большой буфер в векторе, например, в какой-то момент он может выполнить перераспределение и заканчивается использованием до 1,5 x размера памяти, который вы собираетесь использовать в какой-то момент при перераспределении и перемещении данных. (Например, в какой-то момент он выделяет N байтов, вы добавляете данные через append или итератор вставки, и он выделяет 2N байтов, копирует первый N и выпускает N. У вас есть 3n байт, выделенных в какой-то момент).

Так что в конце концов он имеет много преимуществ, и платит, если вы знаете, что вы делаете. Вы должны знать немного о том, как работает C++, чтобы использовать его во встроенных проектах без сюрпризов.

и для парня с фиксированными буферами и сбросом вы всегда можете сбросить внутри нового оператора или что-то еще, если у вас нет памяти, но это будет означать, что вы сделали плохой дизайн, который может исчерпать вашу память.

An исключение создается с помощью 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>

Не кажется таким страшным, и никакие накладные расходы не добавляются внутри {} блоков или функций, если исключение не брошено.

В дополнение ко всем комментариям, я предлагаю вам прочитать технический отчет о производительности C++ что конкретно касается тем, которые вас интересуют: использование C++ во встроенных (включая жесткие системы реального времени); как обычно реализуется обработка исключений и какие накладные расходы она имеет; накладные расходы на выделение свободного хранилища.

отчет действительно хорош, так как развенчивает многие популярные хвосты о производительности C++.

проект с открытым исходным кодом " встроенная библиотека шаблонов (ETL)" нацеливает обычные проблемы с STL, используемым во встроенных приложениях, предоставляя / реализуя библиотеку:

  • детерминированное поведение
  • "создать набор контейнеров, где размер или максимальный размер определяется во время компиляции. Эти контейнеры должны быть в значительной степени эквивалентны тем, которые поставляются в STL, с совместимым API."
  • нет динамической памяти распределение
  • RTTI не требуется
  • мало использования виртуальных функций (только когда это абсолютно необходимо)
  • набор контейнеров фиксированной емкости
  • кэш дружественного хранения контейнеров в виде непрерывно выделенного блока памяти
  • уменьшенный размер кода контейнера
  • типобезопасные интеллектуальные перечисления
  • вычисления CRC
  • контрольные суммы и хэш-функции
  • варианты = вид типа безопасный профсоюзы
  • выбор утверждений, исключений, обработчика ошибок или отсутствие проверок на ошибки
  • сильно и подвергнутое испытанию транспортное средство
  • хорошо документированный исходный код
  • и другие особенности...

вы также можете рассмотреть коммерческий C++ STL для встроенных разработчиков предоставлено E. S. R. Labs.

самая большая проблема с STL во встроенных системах-это проблема выделения памяти (которая, как вы сказали, вызывает много проблем).

Я бы серьезно исследовал создание собственного управления памятью, построенного путем переопределения операторов new/delete. Я уверен, что с немного времени, это можно сделать, и это почти наверняка стоит.

Что касается вопроса исключений, я бы не пошел туда. Исключения составляют серьезный спад ваш код, потому что они причина каждого блока ({ }) иметь код до и после, что позволяет ловить исключение и уничтожение любых объектов, содержащихся внутри. У меня нет жестких данных об этом, но каждый раз, когда я видел эту проблему, я видел подавляющие доказательства массового замедления, вызванного использованием исключений.

Edit:
Так как многие люди писали Комментарии о том, что обработка исключений не медленнее, я думал, что добавлю это небольшая заметка (спасибо за людей, которые написали это в комментариях, я подумал, что было бы хорошо добавить его здесь).

причина, по которой обработка исключений замедляет ваш код, заключается в том, что компилятор должен убедиться, что каждый блок ({}), из места, где исключение выбрасывается в место, с которым оно обрабатывается, должно освобождать любые объекты внутри него. Это код, который добавляется в каждый блок, независимо от того, кто когда-либо бросает исключение или нет (так как компилятор не может сказать во время компиляции будет ли этот блок частью исключительной "цепочки").

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

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