Ответ @ vicatcu довольно исчерпывающий. Еще одна вещь, на которую следует обратить внимание, - это то, что ЦП может переходить в состояния ожидания (циклы зависания ЦП) при доступе к вводу / выводу, включая память программ и данных.
Например, мы используем DSP TI F28335; некоторые области ОЗУ находятся в состоянии ожидания 0 для памяти программ и данных, поэтому, когда вы выполняете код в ОЗУ, он выполняется с 1 циклом на инструкцию (за исключением тех инструкций, которые занимают более 1 цикла). Однако когда вы выполняете код из флэш-памяти (встроенной EEPROM, более или менее), он не может работать на полных 150 МГц и работает в несколько раз медленнее.
Что касается высокоскоростного кода прерывания, вы должны научиться многим вещам.
Во-первых, познакомьтесь с вашим компилятором. Если компилятор делает хорошую работу, он не должен быть намного медленнее, чем сборка вручную. (где «намного медленнее»: с моей стороны будет хорошо коэффициент 2; коэффициент 10 будет неприемлемым) Вам нужно узнать, как (и когда) использовать флаги оптимизации компилятора, и время от времени вы должны искать на выходе компилятора, чтобы увидеть, как это происходит.
Некоторые другие вещи, которые вы можете сделать компилятором для ускорения кода:
используйте встроенные функции (не помню, поддерживает ли это C или это только C ++ - ism), как для небольших функций, так и для функций, которые будут выполняться только один или два раза. Недостатком является то, что встроенные функции трудно отлаживать, особенно если включена оптимизация компилятора. Но они сохраняют вам ненужные последовательности вызова / возврата, особенно если абстракция «функции» предназначена для концептуального проектирования, а не для реализации кода.
Посмотрите в руководстве по вашему компилятору, есть ли в нем встроенные функции - это встроенные функции, зависящие от компилятора, которые отображаются непосредственно в инструкции по сборке процессора; У некоторых процессоров есть инструкции по сборке, которые делают полезные вещи, такие как мин / макс / бит в обратном направлении, и вы можете сэкономить на этом время.
Если вы выполняете численные вычисления, убедитесь, что вы не вызываете функции математической библиотеки без необходимости. У нас был один случай, когда код был похож y = (y+1) % 4
на счетчик с периодом 4, ожидая, что компилятор реализует модуль 4 как побитовое И. Вместо этого это назвало библиотеку математики. Поэтому мы заменили y = (y+1) & 3
на то, что хотели.
Разберитесь с страницей хаки битной-вертелом . Я гарантирую, что вы будете использовать хотя бы один из них часто.
Вам также следует использовать периферийные устройства (таймеры) вашего ЦП для измерения времени выполнения кода - у большинства из них есть таймер / счетчик, который можно настроить для работы на тактовой частоте ЦП. Захватите копию счетчика в начале и конце критического кода, и вы увидите, сколько времени это займет. Если вы не можете этого сделать, другой альтернативой является понижение выходного контакта в начале вашего кода, повышение его в конце и просмотр этого выхода на осциллографе для определения времени выполнения. У каждого подхода есть свои компромиссы: внутренний таймер / счетчик более гибкий (вы можете рассчитать несколько моментов), но сложнее получить информацию, тогда как установка / очистка выходного пина сразу видна в области действия, и вы можете собирать статистику, но трудно различить несколько событий.
Наконец, есть очень важный навык, который приходит с опытом - как общим, так и с конкретными комбинациями процессор / компилятор: знание, когда и когда не следует оптимизировать . В общем, ответ не оптимизировать. Цитата Дональда Кнута часто публикуется в StackOverflow (обычно это только последняя часть):
Мы должны забыть о малой эффективности, скажем, в 97% случаев: преждевременная оптимизация - корень всех зол
Но вы находитесь в ситуации, когда знаете, что вам нужно провести какую-то оптимизацию, поэтому пришло время кусать пули и оптимизировать (или получить более быстрый процессор, или и то, и другое). Вы НЕ писать всю ISR в сборе. Это почти гарантированная катастрофа - если вы сделаете это, в течение месяцев или даже недель вы забудете части того, что вы сделали и почему, и код, вероятно, будет очень хрупким и трудным для изменения. Однако, вероятно, в вашем коде есть части, которые являются хорошими кандидатами для сборки.
Признаки того, что части вашего кода хорошо подходят для кодирования ассемблера:
- функции, которые хорошо содержатся, четко определенные небольшие подпрограммы вряд ли изменятся
- функции, которые могут использовать определенные инструкции по сборке (минимальное / максимальное / правое смещение / и т. д.)
- функции, которые вызываются много раз (вы получаете множитель: если вы сохраняете 0.5usec на каждый вызов, и он вызывается 10 раз, это экономит вам 5 usec, что важно в вашем случае)
Изучите соглашения о вызовах функций вашего компилятора (например, где он помещает аргументы в регистры и какие регистры он сохраняет / восстанавливает), чтобы вы могли писать подпрограммы на C-callable.
В моем текущем проекте у нас есть довольно большая кодовая база с критическим кодом, который должен работать с прерыванием 10 кГц (100usec - звучит знакомо?), И не так много функций, которые написаны на ассемблере. Это вычисления CRC, очереди программного обеспечения, компенсация усиления / смещения АЦП.
Удачи!