Я видел некоторые библиотеки для микроконтроллеров, и их функции выполняют одну вещь за раз. Например, что-то вроде этого:
void setCLK()
{
// Code to set the clock
}
void setConfig()
{
// Code to set the config
}
void setSomethingElse()
{
// 1 line code to write something to a register.
}
Затем идут другие функции, которые используют этот однострочный код, содержащий функцию, для других целей. Например:
void initModule()
{
setCLK();
setConfig();
setSomethingElse();
}
Я не уверен, но я полагаю, что таким образом это создаст больше вызовов переходов и создаст накладные расходы на размещение адресов возврата каждый раз, когда функция вызывается или завершается. И это заставит программу работать медленно, верно?
Я искал, и везде говорят, что большим правилом программирования является то, что функция должна выполнять только одну задачу.
Поэтому, если я напишу непосредственно функциональный модуль InitModule, который устанавливает часы, добавляет некоторую желаемую конфигурацию и делает что-то еще без вызова функций. Это плохой подход при написании встроенного программного обеспечения?
РЕДАКТИРОВАТЬ 2:
Кажется, многие люди поняли этот вопрос так, будто я пытаюсь оптимизировать программу. Нет, я не собираюсь этого делать . Я позволяю компилятору делать это, потому что он будет всегда (надеюсь, что нет!) Лучше меня.
Все обвинения в том, что я выбрал пример, представляющий некоторый код инициализации . Вопрос не имеет намерения касаться вызовов функций, выполняемых для целей инициализации. Мой вопрос заключается в том, имеет ли какое-либо преимущество разбиение определенной задачи на маленькие функции многострочных ( так что inline-строк ), работающих внутри бесконечного цикла, над написанием длинной функции без вложенной функции?
Пожалуйста, учитывайте читабельность, определенную в ответе @Jonk .
источник
Ответы:
Возможно, в вашем примере производительность не будет иметь значения, так как код запускается только один раз при запуске.
Эмпирическое правило, которое я использую: пишите свой код как можно более читабельным и начинайте оптимизацию только в том случае, если вы заметили, что ваш компилятор не выполняет свою магию правильно.
Стоимость вызова функции в ISR может быть такой же, как стоимость вызова функции во время запуска с точки зрения хранения и времени. Тем не менее, временные требования во время этого ISR могут быть гораздо более важными.
Кроме того, как уже отмечалось другими, стоимость (и значение «стоимости») вызова функции различаются в зависимости от платформы, компилятора, настроек оптимизации компилятора и требований приложения. Будет огромная разница между 8051 и cortex-m7, а также кардиостимулятором и выключателем.
источник
Я не могу придумать никакого преимущества (но см. Примечание к JasonS внизу), заключая одну строку кода в функцию или подпрограмму. За исключением, возможно, того, что вы можете назвать функцию как-нибудь «читабельную». Но вы также можете прокомментировать строку. А поскольку завершение строки кода в функции стоит памяти кода, места в стеке и времени выполнения, мне кажется, что это в основном контрпродуктивно. В учебной ситуации? Это может иметь какой-то смысл. Но это зависит от класса учеников, их предварительной подготовки, учебной программы и учителя. В основном, я думаю, что это не очень хорошая идея. Но это мое мнение.
Что подводит нас к сути. Ваша широкая область вопросов в течение десятилетий была предметом некоторых дискуссий и до сих пор остается предметом некоторых дискуссий. Итак, по крайней мере, когда я читаю ваш вопрос, мне кажется, что это вопрос, основанный на мнении (как вы его задали).
Это можно было бы отойти от того, чтобы быть настолько же основанным на мнении, как если бы вы были более подробно о ситуации и тщательно описали цели, которые вы считали основными. Чем лучше вы определите свои инструменты измерения, тем более объективными могут быть ответы.
Вообще говоря, вы хотите сделать следующее для любого кодирования. (Ниже я предполагаю, что мы сравниваем различные подходы, которые все достигают целей. Очевидно, что любой код, который не выполняет необходимые задачи, хуже, чем код, который успешно выполняется, независимо от того, как он написан.)
Вышесказанное в целом верно для всего кодирования. Я не обсуждал использование параметров, локальных или статических глобальных переменных и т. Д. Причина в том, что для встроенного программирования пространство приложения часто накладывает экстремальные и очень существенные новые ограничения, и невозможно обсудить их все без обсуждения каждого встроенного приложения. И этого здесь не происходит, во всяком случае.
Этими ограничениями могут быть любые (и более) из них:
И так далее. (Код проводки для жизненно важных медицинских приборов тоже имеет свой собственный мир.)
В результате встроенное кодирование часто не является бесплатным для всех, где вы можете кодировать, как на рабочей станции. Часто существуют серьезные, конкурентные причины для большого разнообразия очень сложных ограничений. И они могут сильно выступать против более традиционных и фондовых ответов.
Что касается читабельности, я считаю, что код читабелен, если он написан непротиворечивым образом, который я могу выучить, читая его. И там, где нет преднамеренной попытки запутать код. Там действительно не намного больше требуется.
Читаемый код может быть достаточно эффективным и соответствовать всем вышеупомянутым требованиям, которые я уже упоминал. Главное, что вы полностью понимаете, что каждая строка кода, которую вы пишете, производит на уровне сборки или машины, когда вы ее кодируете. C ++ накладывает серьезное бремя на программиста, потому что во многих ситуациях идентичные фрагменты кода C ++ фактически генерируют разные фрагменты машинного кода, которые имеют совершенно разные характеристики. Но C, как правило, в основном "то, что вы видите, то и получаете". Так что в этом отношении безопаснее.
РЕДАКТИРОВАТЬ за JasonS:
Я использую C с 1978 года, а C ++ примерно с 1987 года, и у меня большой опыт использования как для мейнфреймов, так и для миникомпьютеров и (в основном) встроенных приложений.
Джейсон приводит комментарий об использовании «inline» в качестве модификатора. (С моей точки зрения, это относительно «новая» возможность, потому что она просто не существовала, возможно, половину моей жизни или более с использованием C и C ++.) Использование встроенных функций может фактически делать такие вызовы (даже для одной строки код) довольно практично. И это гораздо лучше, где это возможно, чем использование макроса из-за типизации, которую может применить компилятор.
Но есть и ограничения. Во-первых, вы не можете полагаться на то, что компилятор «поймет подсказку». Может или не может. И есть веские причины, чтобы не понять намек. (Для очевидного примера, если адрес функции взят, для этого требуется создание экземпляра функции, а использование адреса для выполнения вызова ... потребует вызова. Тогда код не может быть встроен.) и другие причины. Компиляторы могут иметь широкий спектр критериев, по которым они судят, как обрабатывать подсказку. И как программист, это означает, что вы должныпотратьте некоторое время на изучение этого аспекта компилятора, иначе вы, скорее всего, примете решение, основываясь на ошибочных идеях. Таким образом, это увеличивает нагрузку как на автора кода, так и на любого читателя, а также на любого, кто планирует перенести код на другой компилятор.
Также компиляторы C и C ++ поддерживают раздельную компиляцию. Это означает, что они могут скомпилировать один фрагмент кода C или C ++ без компиляции любого другого связанного кода для проекта. Для того, чтобы встроить код, предполагая, что компилятор в противном случае может сделать это, он должен не только иметь объявление «в области видимости», но и также иметь определение. Обычно программисты работают, чтобы убедиться, что это так, если они используют «встроенный». Но ошибки легко закрасться.
В целом, хотя я также использую inline там, где считаю нужным, я склонен полагать, что не могу на это полагаться. Если производительность является существенным требованием, и я думаю, что OP уже ясно написал, что при переходе на более «функциональный» маршрут имело место значительное снижение производительности, тогда я, конечно, предпочел бы избегать использования inline как практики кодирования и вместо этого следовал бы немного другому, но полностью согласованному образцу написания кода.
Последнее замечание о том, что «inline» и определения находятся «в области видимости» для отдельного этапа компиляции. Возможно (не всегда надежно) выполнение работ на этапе связывания. Это может произойти в том и только в том случае, если компилятор C / C ++ внедряет в объектные файлы достаточно деталей, чтобы компоновщик мог выполнять «встроенные» запросы. Лично у меня не было системы компоновщика (вне Microsoft), которая бы поддерживала эту возможность. Но это может произойти. Опять же, следует ли полагаться на это или нет, будет зависеть от обстоятельств. Но я обычно предполагаю, что это не было переложено на компоновщик, если я не знаю иначе, основываясь на убедительных доказательствах. И если я на это полагаюсь, это будет задокументировано на видном месте.
C ++
Для тех, кто интересуется, вот пример того, почему я остаюсь достаточно осторожным в отношении C ++ при кодировании встроенных приложений, несмотря на его готовность сегодня. Я выбросить некоторые термины , которые я думаю , что все встроенные программистам C ++ нужно знать холод :
Это всего лишь короткий список. Если вы еще не знаете все об этих терминах и почему я их перечислил (и многие другие я не перечислил здесь), то я бы посоветовал не использовать C ++ для встроенной работы, если это не вариант для проекта. ,
Давайте кратко рассмотрим семантику исключений в C ++, чтобы получить представление.
Компилятор C ++ видит первый вызов foo () и может просто позволить размотке обычного кадра активации произойти, если foo () выдает исключение. Другими словами, компилятор C ++ знает, что в этот момент не требуется никакого дополнительного кода для поддержки процесса раскрутки фрейма, участвующего в обработке исключений.
Но как только String s создан, компилятор C ++ знает, что он должен быть должным образом уничтожен, прежде чем можно будет разрешить раскрутку кадра, если позднее произойдет исключение. Таким образом, второй вызов foo () семантически отличается от первого. Если второй вызов функции foo () генерирует исключение (что он может или не может делать), компилятор должен разместить код, предназначенный для обработки уничтожения String, прежде чем позволить обычному размотке кадра произойти. Это отличается от кода, необходимого для первого вызова foo ().
(Можно добавить дополнительные декорации в C ++, чтобы помочь ограничить эту проблему. Но дело в том, что программисты, использующие C ++, просто должны гораздо лучше понимать последствия каждой строки кода, которую они пишут.)
В отличие от malloc C, новый C ++ использует исключения, чтобы сигнализировать, когда он не может выполнить необработанное выделение памяти. Так будет и «dynamic_cast». (См. 3-е изд. Страуструпа «Язык программирования C ++», стр. 384 и 385, для стандартных исключений в C ++.) Компиляторы могут разрешить отключение этого поведения. Но в целом вы будете подвергаться некоторым накладным расходам из-за правильно сформированных прологов и эпилогов обработки исключений в сгенерированном коде, даже когда исключения на самом деле не имеют места и даже когда скомпилированная функция фактически не имеет каких-либо блоков обработки исключений. (Страуструп публично посетовал на это.)
Без частичной специализации шаблонов (не все компиляторы C ++ поддерживают это) использование шаблонов может привести к катастрофе для встроенного программирования. Без этого расцвет кода будет серьезным риском, который может убить встроенный проект с небольшой памятью в одно мгновение.
Когда функция C ++ возвращает объект, временный компилятор без имени создается и уничтожается. Некоторые компиляторы C ++ могут предоставить эффективный код, если в операторе return используется конструктор объекта вместо локального объекта, что уменьшает потребность в строительстве и разрушении одним объектом. Но не каждый компилятор делает это, и многие программисты на C ++ даже не знают об этой «оптимизации возвращаемого значения».
Предоставление конструктора объекта с одним типом параметра может позволить компилятору C ++ найти путь преобразования между двумя типами совершенно неожиданным для программиста способом. Такое «умное» поведение не является частью C.
Предложение catch, задающее базовый тип, будет «разрезать» брошенный производный объект, потому что брошенный объект копируется с использованием «статического типа» предложения catch, а не «динамического типа» объекта. Весьма распространенный источник страданий от исключений (когда вы чувствуете, что можете даже позволить себе исключения во встроенном коде.)
Компиляторы C ++ могут автоматически генерировать для вас конструкторы, деструкторы, конструкторы копирования и операторы присваивания с непредвиденными результатами. Требуется время, чтобы получить средства с деталями этого.
Передача массивов производных объектов в функцию, принимающую массивы базовых объектов, редко генерирует предупреждения компилятора, но почти всегда приводит к некорректному поведению.
Поскольку C ++ не вызывает деструктор частично сконструированных объектов, когда в конструкторе объектов возникает исключение, обработка исключений в конструкторах обычно требует «умных указателей», чтобы гарантировать, что сконструированные фрагменты в конструкторе будут должным образом уничтожены, если там произойдет исключение. , (См. Страуструп, стр. 367 и 368.) Это распространенная проблема при написании хороших классов на C ++, но, конечно же, ее избегают в C, так как C не имеет встроенной семантики построения и уничтожения. Написание правильного кода для обработки конструкции подобъекты внутри объекта означают написание кода, который должен справиться с этой уникальной семантической проблемой в C ++; Другими словами, «написание вокруг» C ++ семантического поведения.
C ++ может копировать объекты, переданные в параметры объекта. Например, в следующих фрагментах вызов "rA (x);" может заставить компилятор C ++ вызывать конструктор для параметра p, чтобы затем вызвать конструктор копирования для передачи объекта x в параметр p, затем другой конструктор для возвращаемого объекта (неназванного временного) функции rA, которая, конечно, скопировано из параметра p. Хуже того, если у класса А есть свои собственные объекты, которые нуждаются в строительстве, это может привести к катастрофическим телескопам. (Программист AC избежал бы большей части этого мусора, оптимизируя вручную, поскольку программисты на C не имеют такого удобного синтаксиса и должны выражать все детали по одному).
Наконец, короткое примечание для программистов на Си. longjmp () не имеет переносимого поведения в C ++. (Некоторые программисты на C используют это как своего рода механизм «исключения».) Некоторые компиляторы C ++ на самом деле пытаются настроить вещи для очистки, когда берется longjmp, но это поведение не переносимо в C ++. Если компилятор очищает построенные объекты, он не переносим. Если компилятор не очищает их, то объекты не уничтожаются, если код покидает область действия созданных объектов в результате longjmp и поведение недопустимо. (Если использование longjmp в foo () не выходит из области видимости, поведение может быть нормальным.) Это не слишком часто используется программистами на C, но они должны знать об этих проблемах перед их использованием.
источник
inline static void turnOnFan(void) { PORTAbits &= ~(1<<8); }
который называют во многих местах, является идеальным кандидатом.1) Код для удобочитаемости и ремонтопригодности в первую очередь. Наиболее важным аспектом любой кодовой базы является то, что она хорошо структурирована. Красиво написанное программное обеспечение, как правило, имеет меньше ошибок. Вам может потребоваться внести изменения через пару недель / месяцев / лет, и это очень поможет, если ваш код будет приятно читать. Или, может быть, кто-то другой должен внести изменения.
2) Производительность кода, который выполняется один раз, не имеет большого значения. Забота о стиле, а не о производительности
3) Даже код в тесных циклах должен быть корректным в первую очередь. Если у вас возникли проблемы с производительностью, оптимизируйте, как только код верен.
4) Если вам нужно оптимизировать, вы должны измерить! Неважно, думаете ли вы, или кто-то говорит вам, что
static inline
это просто рекомендация для компилятора. Вы должны взглянуть на то, что делает компилятор. Вы также должны измерить, действительно ли встраивание улучшило производительность. Во встроенных системах вы также должны измерять размер кода, так как память кода обычно довольно ограничена. Это самое важное правило, которое отличает инженерию от догадок. Если вы не измеряли это, это не помогло. Инженерия измеряет. Наука это записывает;)источник
Когда функция вызывается только в одном месте (даже внутри другой функции), компилятор всегда помещает код в это место, вместо того, чтобы действительно вызывать функцию. Если функция вызывается во многих местах, то имеет смысл использовать функцию хотя бы с точки зрения размера кода.
После компиляции кода не будет нескольких вызовов, вместо этого читаемость будет значительно улучшена.
Также вы захотите иметь, например, код инициализации ADC в той же библиотеке, что и другие функции ADC, которых нет в главном файле c.
Многие компиляторы позволяют вам задавать разные уровни оптимизации для скорости или размера кода, поэтому, если у вас есть небольшая функция, вызываемая во многих местах, тогда функция будет «встроенной», скопированной туда вместо вызова.
Оптимизация по скорости встроит функции в максимально возможном количестве мест, оптимизация по размеру кода вызовет функцию, однако, когда функция вызывается только в одном месте, как в вашем случае, она всегда будет «встроенной».
Код как это:
будет компилировать в:
без использования какого-либо вызова.
И ответ на ваш вопрос, в вашем примере или аналогичном, читаемость кода не влияет на производительность, ничего не много по скорости или размеру кода. Распространено использование нескольких вызовов только для того, чтобы сделать код читабельным, в конце они будут выполнены как встроенный код.
Обновите, чтобы указать, что приведенные выше утверждения недействительны для специально урезанных компиляторов бесплатной версии, таких как бесплатная версия Microchip XCxx. Подобные вызовы функций - это золотая жила для Microchip, чтобы показать, насколько лучше платная версия, и если вы скомпилируете ее, вы найдете в ASM ровно столько же вызовов, сколько и в C-коде.
Также это не для глупых программистов, которые ожидают использовать указатель на встроенную функцию.
Это раздел электроники, а не общий раздел C C ++ или раздел программирования, речь идет о программировании на микроконтроллерах, где любой приличный компилятор будет выполнять вышеуказанную оптимизацию по умолчанию.
Поэтому, пожалуйста, прекратите подавлять голоса только потому, что в редких, необычных случаях это может быть не так.
источник
Во-первых, нет лучшего или худшего; это все вопрос мнения. Вы очень правы, что это неэффективно. Это может быть оптимизировано или нет; По-разному. Обычно вы видите эти типы функций, часы, GPIO, таймер и т. Д. В отдельных файлах / каталогах. Компиляторы, как правило, не смогли оптимизировать эти пробелы. Есть одна, о которой я знаю, но не очень широко используемая для подобных вещей.
Отдельный файл:
Выбор цели и компилятора для демонстрационных целей.
Это то, что большинство ответов здесь говорит вам, что вы наивны, что все это оптимизируется и функции удаляются. Ну, они не удаляются, так как они определены глобально по умолчанию. Мы можем удалить их, если они не нужны, за пределами этого файла.
удаляет их сейчас, когда они встроены.
Но реальность такова, когда вы берете на себя чип производителя или библиотеки BSP,
Вы наверняка начнете добавлять накладные расходы, которые имеют заметные затраты на производительность и пространство. От нескольких до пяти процентов каждого в зависимости от того, насколько мала каждая функция.
Почему это все равно делается? Некоторые из них - это набор правил, которые преподаватели будут или все еще учат, чтобы упростить классификацию кода. Функции должны помещаться на странице (назад, когда вы печатали свою работу на бумаге), не делайте этого, не делайте этого и т. Д. Многое из этого - создание библиотек с общими именами для разных целей. Если у вас есть десятки семейств микроконтроллеров, некоторые из которых имеют общие периферийные устройства, а некоторые нет, возможно, три или четыре разных варианта UART смешаны между семействами, разные GPIO, контроллеры SPI и т. Д. Вы можете иметь общую функцию gpio_init (), get_timer_count () и т. д. И повторно используйте эти абстракции для различных периферийных устройств.
Это становится делом в основном ремонтопригодности и дизайна программного обеспечения, с некоторой возможной читабельностью. Поддерживаемость, удобочитаемость и производительность, которой вы не можете достичь; Вы можете выбрать только один или два за один раз, а не все три.
Это в значительной степени основанный на мнении вопрос, и вышеизложенное показывает три основных пути, которыми это может пойти. Что касается того, какой путь ЛУЧШИЙ, то это строгое мнение. Делает ли всю работу в одной функции? Вопрос, основанный на мнении, некоторые люди склонны к производительности, некоторые определяют модульность и свою версию читабельности как ЛУЧШИЙ. Интересная проблема, которую многие называют «читабельностью», чрезвычайно болезненна; Чтобы «увидеть» код, вам нужно одновременно открыть 50–10 000 файлов и как-то попытаться линейно увидеть функции в порядке выполнения, чтобы увидеть, что происходит. Я считаю, что это противоположно удобочитаемости, но другие находят его читабельным, поскольку каждый элемент помещается в окне экрана / редактора и может использоваться целиком после того, как они запомнят вызываемые функции и / или имеют редактор, который может входить и выходить из каждая функция в проекте.
Это еще один важный фактор, когда вы видите различные решения. Текстовые редакторы, IDE и т. Д. Являются очень личными, и это выходит за рамки vi против Emacs. Эффективность программирования, количество строк в день / месяц увеличивается, если вам удобно и эффективно использовать инструмент, который вы используете. Возможности инструмента могут / будут намеренно или не склоняться к тому, как фанаты этого инструмента пишут код. И в результате, если один человек пишет эти библиотеки, проект в какой-то степени отражает эти привычки. Даже если это команда, привычки / предпочтения ведущего разработчика или босса могут быть навязаны остальной части команды.
Стандарты кодирования, в которых скрыто множество личных предпочтений, опять же очень религиозные vi против Emacs, табуляция против пробелов, расположение скобок и т. Д. И они играют в некоторой степени то, как библиотеки спроектированы.
Как вы должны написать свое? Как бы вы ни хотели, на самом деле нет неправильного ответа, если он работает. Конечно, существует плохой или рискованный код, но если он написан так, что вы можете поддерживать его по мере необходимости, он отвечает вашим целям проектирования, разочаровывается в удобочитаемости и некотором удобстве обслуживания, если важна производительность, или наоборот. Вам нравятся короткие имена переменных, чтобы одна строка кода соответствовала ширине окна редактора? Или длинные чрезмерно описательные имена, чтобы избежать путаницы, но читаемость снижается, потому что вы не можете получить одну строку на странице; теперь это визуально разбито, возиться с потоком.
Вы не собираетесь ударить домой бежать в первый раз в летучей мыши. Могут / должны пройти десятилетия, чтобы по-настоящему определить ваш стиль. В то же время со временем ваш стиль может измениться: наклониться в одну сторону, а затем - в другую.
Вы услышите много не оптимизировать, никогда не оптимизировать и преждевременной оптимизации. Но, как показано, такие конструкции с самого начала создают проблемы с производительностью, затем вы начинаете видеть хаки для решения этой проблемы, а не переделывать проект с самого начала. Я согласен, что есть ситуации, одна функция - несколько строк кода, которые вы можете попытаться с трудом манипулировать компилятором, основываясь на страхе того, что компилятор собирается делать иначе (заметьте, с опытом этот вид кодирования становится простым и естественным, оптимизируя, когда вы пишете, зная, как компилятор собирается скомпилировать код), затем вы хотите подтвердить, где на самом деле находится стилер цикла, прежде чем атаковать его.
Вы также должны в некоторой степени разработать свой код для пользователя. Если это ваш проект, вы являетесь единственным разработчиком; это все, что вы хотите. Если вы пытаетесь создать библиотеку для раздачи или продажи, вы, вероятно, захотите, чтобы ваш код выглядел как все остальные библиотеки, от сотен до тысяч файлов с крошечными функциями, длинными именами функций и длинными именами переменных. Несмотря на проблемы с читабельностью и производительностью, в IMO вы найдете больше людей, которые смогут использовать этот код.
источник
Очень общее правило - компилятор может оптимизировать лучше, чем вы. Конечно, есть исключения, если вы делаете очень интенсивные циклы, но в целом, если вы хотите хорошую оптимизацию для скорости или размера кода, выбирайте свой компилятор с умом.
источник
Это наверняка зависит от вашего собственного стиля кодирования. Одно общее правило, которое существует, состоит в том, что имена переменных, а также имена функций должны быть как можно более понятными и понятными. Чем больше вложенных вызовов или строк кода вы помещаете в функцию, тем сложнее становится определить четкую задачу для этой одной функции. В вашем примере у вас есть функция,
initModule()
которая инициализирует вещи и вызывает подпрограммы, которые затем устанавливают часы или устанавливают конфигурацию . Вы можете сказать это, просто прочитав название функции. Если вы поместите весь код из подпрограммinitModule()
непосредственно в вашу программу, станет менее очевидно, что на самом деле делает функция. Но, как часто, это просто руководство.источник
Если функция действительно делает только одну очень маленькую вещь, подумайте о ее создании
static inline
.Добавьте его в файл заголовка вместо файла C и используйте слова
static inline
для его определения:Теперь, если функция даже немного длиннее, например, содержит более 3 строк, было бы неплохо избежать ее
static inline
и добавить в файл .c. В конце концов, встроенные системы имеют ограниченную память, и вы не хотите слишком сильно увеличивать размер кода.Кроме того, если вы определите функцию
file1.c
и используете ееfile2.c
, компилятор не будет автоматически вставлять ее. Однако, если вы определите этоfile1.h
какstatic inline
функцию, скорее всего, ваш компилятор вставит его в строку.Эти
static inline
функции чрезвычайно полезны в высокопроизводительном программировании. Я обнаружил, что они часто повышают производительность кода более чем в три раза.источник
file1.c
и используете ееfile2.c
, компилятор не будет автоматически вставлять ее. Ложные . Смотрите, например,-flto
в gcc или clang.Одна трудность при попытке написать эффективный и надежный код для микроконтроллеров состоит в том, что некоторые компиляторы не могут надежно обрабатывать определенную семантику, если код не использует директивы, специфичные для компилятора, или не отключает многие оптимизации.
Например, если есть одноядерная система с подпрограммой обработки прерываний [запускается по таймеру или как-то еще]:
должна быть возможность написания функций для запуска фоновой операции записи или ожидания ее завершения:
а затем вызвать такой код, используя:
К сожалению, с включенной полной оптимизацией «умный» компилятор, такой как gcc или clang, решит, что первый набор записей никак не повлияет на наблюдаемую программу, и, таким образом, их можно будет оптимизировать. Качественные компиляторы, такие как
icc
, менее склонны делать это, если установка прерывания и ожидание завершения включает в себя как энергозависимые записи, так и энергозависимые чтения (как в данном случае), но платформа, на которую ориентируется,icc
не так популярна для встроенных систем.Стандарт преднамеренно игнорирует проблемы качества реализации, полагая, что существует несколько разумных способов, с помощью которых вышеуказанная конструкция может быть обработана:
Качественные реализации, предназначенные исключительно для таких полей, как сжатие чисел высокого класса, вполне могут ожидать, что код, написанный для таких полей, не будет содержать конструкции, подобные описанным выше.
Качественная реализация может обрабатывать все обращения к
volatile
объектам так, как будто они могут инициировать действия, которые будут обращаться к любому объекту, который виден внешнему миру.Простая, но достойная реализация, предназначенная для использования во встроенных системах, может обрабатывать все вызовы функций, не помеченных как «встроенные», как если бы они могли обращаться к любому объекту, который был выставлен внешнему миру, даже если он не обрабатывает,
volatile
как описано в # 2.Стандарт не пытается предложить, какой из вышеуказанных подходов будет наиболее подходящим для качественной реализации, или требовать, чтобы «соответствующие» реализации были достаточно хорошего качества, чтобы их можно было использовать для любой конкретной цели. Следовательно, некоторые компиляторы, такие как gcc или clang, фактически требуют, чтобы любой код, желающий использовать этот шаблон, был скомпилирован с отключением многих оптимизаций.
В некоторых случаях, убедившись, что функции ввода-вывода находятся в отдельном модуле компиляции, и у компилятора не останется иного выбора, кроме как предположить, что они могут получить доступ к любому произвольному подмножеству объектов, которые были выставлены внешнему миру, может быть разумным минимумом - of of Evils способ написания кода, который будет надежно работать с gcc и clang. В таких случаях, однако, цель состоит не в том, чтобы избежать дополнительных затрат на ненужный вызов функции, а скорее в том, чтобы принять ненужные затраты в обмен на получение требуемой семантики.
источник
volatile
доступы так, как будто они потенциально могут инициировать произвольный доступ к другим объектам), но по какой-то причине gcc и clang скорее будут рассматривать проблемы качества реализации как приглашение вести себя бесполезно.buff
он не объявленvolatile
, он не будет рассматриваться как переменная переменная, доступ к нему может быть переупорядочен или полностью оптимизирован, если, очевидно, не будет использован позже. Правило простое: пометьте все переменные, к которым можно обращаться за пределами обычного потока программы (как это видно из компилятора), какvolatile
.buff
Доступ к содержимому осуществляется в обработчике прерываний? Да. Тогда так и должно бытьvolatile
.magic_write_count
ноль, хранилище принадлежит основной линии. Когда он ненулевой, он принадлежит обработчику прерываний. Для созданияbuff
volatile потребуется, чтобы каждая функция, где бы она ниvolatile
работала, использовала квалифицированные указатели, что значительно ухудшило бы оптимизацию, чем наличие компилятора ...