Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Оптимизация прерываний
Форум разработчиков электроники ELECTRONIX.ru > Сайт и форум > В помощь начинающему > Программирование
Dmitro25
Здравствуйте.
Пишу проект на IAR for AVR.
Возникло желание посмотреть, какой код формирует компилятор для прерываний. Оказалось, что в начале процедуры прерывания сохраняется в data-stack, а конце - восстанавливается 15 регистров общего назначения, хотя вызываемые из прерывания функции не требуют такого количества регистров. Я сначала подумал, что всё дело в том, что вызываемая из прерывания функция находится в другом модуле проекта, перенёс её в тот же модуль, где находится процедура прерывания, но отличий не увидел. Такое же ненужное сохранение множества регистров.
Разумное сохранение регистров я увидел только тогда, когда компилятор включил текст вызываемой функции в процедуру прерывания (inline). Если по какой-то причине inline функции не получается, снова бессмысленное сохранение регистров.
Чтобы не быть голословным, привожу код:
Код
#include <ioavr.h>

#pragma optimize=no_inline
void DoNothing()
{
}

#pragma vector = TIMER1_COMPA_vect
__interrupt void ext_int0_isr_routine(void)
{
  DoNothing();
}

int main( void )
{
  return 0;
}

Оптимизация стоит максимальная по скорости.
Подскажите, как этого можно избежать? Очень хотелось бы из прерывания вызывать функции, в т.ч. из других модулей, и иметь при этом оптимальный код.
mempfis_
Код
Возникло желание посмотреть, какой код формирует компилятор для прерываний.  Оказалось, что в начале процедуры прерывания сохраняется в data-stack, а конце - восстанавливается 15 регистров общего назначения, хотя вызываемые из прерывания функции не требуют такого количества регистров.


Попробуйте вместо вызова функции в прерывании устанавливать глобальный флаг. А восновном цикле анализировать - если флаг установлен то необходим вызов этой функции.
Если в прерывании идёт вызов функций то иар на всякий случай сохраняет все регистрыю Если же нет вызова - то сохраняются только те которые он планирует использовать в прерывании.
Dmitro25
2mempfis:
Цитата
Попробуйте вместо вызова функции в прерывании устанавливать глобальный флаг. А в основном цикле анализировать - если флаг установлен то необходим вызов этой функции.

Подход понятный, и я его использую. Но в некоторых случаях хочется всё же немедленно выполнить какие-то действия.
Цитата
Если в прерывании идёт вызов функций то иар на всякий случай сохраняет все регистры

Вот это мне и непонятно. Зачем?
MrYuran
Цитата(Dmitro25 @ Mar 31 2010, 09:47) *
Подход понятный, и я его использую. Но в некоторых случаях хочется всё же немедленно выполнить какие-то действия.

Попробуйте принудительно заинлайнить функцию. Лишние вызовы внутри прерывания оптимальности не добавляют.
dxp
Цитата(Dmitro25 @ Mar 31 2010, 13:47) *
Вот это мне и непонятно. Зачем?

Как раз понятное и объяснимое поведение. По-другому просто и быть не может. У IAR есть соглашения о вызовах функций в части распределения регистров. Все регистры в этом контексте делятся на две группы: local и scratch. Регистры группы scratch могут использоваться внутри любой функции без сохранения, регистры из группы local, если компилятор "хочет" использовать этот регистр, он должен его значение сначала сохранить, перед выходом восстановить. Этим достигается целостность данных при вызове подпрограмм (функций).

При обычной работе программы комплятор перед вызовом функции не хранит в scratch регистрах ничего ценного - вызываемая функция их будет использовать без сохранения, компилятор об этом "знает" и все ценное хранит в local регистрах и в стеке. Теперь представьте, что вызвался обработчик прерываний (ISR). Как вы знаете, внутри ISR абсолютно все регистры, которые компилятор использует, он обязан сохранить/восстановить - это происходит в силу того, что ISR вызывается асинхронно по отношению к выполнению основной программы, что означает, что при входе в ISR компилятор ничего не знает о состоянии контекста прерванной программы (состоянии регистров в частности - может там все было прервано в момент сохранения значений local регистров и или данных из scratch регистров в стек). И компилятор, видя код, решает сколько ему надо регистров, их он и сохраняет/восстанавливает.

Теперь представьте, что внутри ISR вызывается функция. Если компилятор видит ее код (функция встраиваемая), то ситуация сводится к вышеописанной. А если функция невстраиваемая, то компилятор просто не знает, какие из scratch регистров могут внутри нее использоваться, поэтому он просто обязан сохранить их все во избежание нарушения целостности данных и неправильной работы программы.

Поэтому либо не вызывайте функции из ISR, либо делайте их гарантировано встраиваемыми. Для этого есть хорошая прагма #pragma inline=forced.

У AVR, насколько помню, к scratch регистрам относятся r16-r25, r30-r31 и r0-r4. Вот они и должны сохраняться всегда.
Dmitro25
2dxp:
Цитата
Как раз понятное и объяснимое поведение. По-другому просто и быть не может

Ваш пост не дописан... Но, по-моему, если следовать разуму, зачем компилятору сохранять регистры перед вызовом функции, внутри которой, как ему ТОЧНО известно, ни один из этих регистров не используется.
Приведите пример, в котором только такое поведение разумно.

2MrYuran:
Цитата
Попробуйте принудительно заинлайнить функцию

Я понимаю, что, скорее всего, придётся делать именно так.
Просто изначально я хотел вызывать из прерывания функции, расположенные в других модулях проекта. Задача была вот в чём: создать для каждого периферийного устройства свой модуль в проекте (для инкапсуляции), а по прерываниям в основном модуле проекта вызывать соответствующие функции модулей периферийных устройств. При этом структура проекта получается более "прозрачной": в основном модуле сосредоточены все прерывания, а каждый из подключаемых модулей - это некий "чёрный ящик" с входом для прерываний и набором других входов и выходов в виде функций.
Однако, сделать процедуру прерывания в другом модуле "inline" у меня не получилось. Без специальных ключей линкер её выбрасывает, а если ставить "#pragma object_attribute=__root", всё равно это транслируется в "call" из прерывания.
Ещё, кстати, один вопрос к оптимальности трансляции, почему в "call", а не в rcall, если пока размер кода проекта не превышает 2кБ?
dxp
Цитата(Dmitro25 @ Mar 31 2010, 15:28) *
2dxp:

Ваш пост не дописан...
Глюк форума. Все дописано.

Цитата(Dmitro25 @ Mar 31 2010, 15:28) *
Но, по-моему, если следовать разуму, зачем компилятору сохранять регистры перед вызовом функции, внутри которой, как ему ТОЧНО известно, ни один из этих регистров не используется.

Это откуда ему "ТОЧНО известно" это? Вот представьте, что вы компилятор, вам дали обработчик прерывания, в нем есть вызов функции, но кроме вызова ничего нет - самой функции вы не видите, ее исходного кода у вас нет, есть только прототип, чтобы вы могли ее правильно вызвать. Все.

Цитата(Dmitro25 @ Mar 31 2010, 15:28) *
Приведите пример, в котором только такое поведение разумно.

Вызов из ISR любой функции, представление которой в точке вызова неизвестно. См выше.

Цитата(Dmitro25 @ Mar 31 2010, 15:28) *
Я понимаю, что, скорее всего, придётся делать именно так.

И никак иначе, если не хотите тупого оверхеда.

Цитата(Dmitro25 @ Mar 31 2010, 15:28) *
Просто изначально я хотел вызывать из прерывания функции, расположенные в других модулях проекта. Задача была вот в чём: создать для каждого периферийного устройства свой модуль в проекте (для инкапсуляции), а по прерываниям в основном модуле проекта вызывать соответствующие функции модулей периферийных устройств. При этом структура проекта получается более "прозрачной": в основном модуле сосредоточены все прерывания, а каждый из подключаемых модулей - это некий "чёрный ящик" с входом для прерываний и набором других входов и выходов в виде функций.

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

Цитата(Dmitro25 @ Mar 31 2010, 15:28) *
Однако, сделать процедуру прерывания в другом модуле "inline" у меня не получилось.

Естественно. Для того, чтобы функция могла быть встроенной, компилятор должен иметь ее полный код в точке вызова - иначе как он будет встраивать? Точнее, что он будет встраивать, если он не видит потрохов функции? Иными словами, встраиваемая функция должна быть определена до точки ее вызова (хоть в заголовочном файле).

Цитата(Dmitro25 @ Mar 31 2010, 15:28) *
Ещё, кстати, один вопрос к оптимальности трансляции, почему в "call", а не в rcall, если пока размер кода проекта не превышает 2кБ?

А откуда компилятор знает о размере кода проекта? Размер кода проекта знает только линкер и то уже после сборки всего проекта. А инструкции вызова ставит компилятор. Если хотите rcall, задавайте опции процессора (-v), которые скажут компилятору, что памяти программ меньше 8к, и тогда компилятор будет ставить rcall.
Сергей Борщ
Цитата(Dmitro25 @ Mar 31 2010, 10:28) *
перед вызовом функции, внутри которой, как ему ТОЧНО известно, ни один из этих регистров не используется.
...
Просто изначально я хотел вызывать из прерывания функции, расположенные в других модулях проекта.
Нестыковка. Как ему будет ТОЧНО известно, если функции в других модулях? Объявляйте их static (static inline, если компилятор позволяет) и выносите в заголовочный файл соответствующего модуля.
Dmitro25
2dxp:
Спасибо за советы.
Всё-таки не удержусь от такого комментария:
Цитата
Это откуда ему "ТОЧНО известно" это? Вот представьте, что вы компилятор, вам дали обработчик прерывания, в нем есть вызов функции, но кроме вызова ничего нет - самой функции вы не видите, ее исходного кода у вас нет, есть только прототип, чтобы вы могли ее правильно вызвать. Все.

В приведённом мною выше примере вызываемая функция расположена прямо в том же модуле, что и процедура прерывания, а результат тот же. Одно дело, если компилятору в качестве одного из модулей проекта предоставляется уже скомпилированный объектный файл и заголовочный - в этом случае трудно делать какие-то предположения об используемых в каждой функции регистрах. Во многих случаях компилятору известен исходный код подключаемых модулей проекта. Почему бы не сделать дополнительную оптимизацию, тем более, что для прерываний это весьма актуально?
Мне кажется, это происходит из-за того, что компилятор и линкер - это две разные программы. Если бы был некий компилятор+линкер, оптимизировать было бы легче. Хотя для исключения сохранения лишних регистров в прерывании достаточно было бы передать из компилятора в линкер список используемых в функции регистров, а также список подфункций, вызываемых из функции.
А насчёт замечания про "call": мне кажется, что задача превращения части инструкций "call" в "rcall" для коротких переходов вполне решаемая для компилятора. Это позволило бы немного сократить код и увеличить скорость. И, кстати, проверил, в CodeVisionAVR в данном случае функция из прерывания вызывается по команде "rcall".

2Сергей Борщ:
Цитата
Объявляйте их static (static inline, если компилятор позволяет) и выносите в заголовочный файл соответствующего модуля.

Если я правильно понял, Вы предлагаете в заголовочный файл вынести реализацию функции?
Я уже так и сделал, например, с функцией spi(). Однако, если данная функция должна иметь доступ к внутренним переменным модуля, возникают проблемы.
Сергей Борщ
Цитата(Dmitro25 @ Mar 31 2010, 12:31) *
Если я правильно понял, Вы предлагаете в заголовочный файл вынести реализацию функции?
Я уже так и сделал, например, с функцией spi(). Однако, если данная функция должна иметь доступ к внутренним переменным модуля, возникают проблемы.
Тогда внесите обработчик в сишный файл этого модуля.
dxp
Цитата(Dmitro25 @ Mar 31 2010, 17:31) *
В приведённом мною выше примере вызываемая функция расположена прямо в том же модуле, что и процедура прерывания, а результат тот же. Одно дело, если компилятору в качестве одного из модулей проекта предоставляется уже скомпилированный объектный файл и заголовочный - в этом случае трудно делать какие-то предположения об используемых в каждой функции регистрах. Во многих случаях компилятору известен исходный код подключаемых модулей проекта. Почему бы не сделать дополнительную оптимизацию, тем более, что для прерываний это весьма актуально?

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

И второе. Компилятор не обязан встраивать функцию, исходя только лишь из наличия квалификатора inline в описании функции. Это слово - просто подсказка компилятору, что эту функцию хорошо бы встроить. Такое поведение определено по стандарту языка. У IAR есть расширение (#pragma inline=forced), с помощью которого можно жестко потребовать встраивания (если оно невозможно, то должна выдаваться ошибка).

Цитата(Dmitro25 @ Mar 31 2010, 17:31) *
Мне кажется, это происходит из-за того, что компилятор и линкер - это две разные программы. Если бы был некий компилятор+линкер, оптимизировать было бы легче. Хотя для исключения сохранения лишних регистров в прерывании достаточно было бы передать из компилятора в линкер список используемых в функции регистров, а также список подфункций, вызываемых из функции.

В некоторых тулчейнах есть специальная post-link оптимизация, когда оптимизатор напускается на уже слинкованный образ. Это дает возможность, главным образом уменьшить размер программы. Но в вашем случае это не поможет.

Заниматься передачей в линкер регистров - пустая затея, ведь код уже скомпилирован, линкер не может его менять, у линкера только задача сложить все вместе и подставить реальные значения адресов.
Dmitro25
2Сергей Борщ:
Цитата
Тогда внесите обработчик в сишный файл этого модуля.

Это тоже понятно. Но как быть, например, если внутри прерывания по таймеру необходимо вызвать несколько функций из разных модулей?
Пример:
Код
#pragma vector = TIMER1_COMPA_vect
__interrupt void ext_Timer1_Comp_isr_routine(void)
{
  sd_tick_10ms();
  keydisplay_tick_10ms();
  mdm_tick_10ms();
}
MALLOY2
Вам же говорили глобальный флаг, а в основном цикле его опрашиваете и выполняете свои процедуры, правда джитетер появится некий но тут уже по задаче надо смотреть.
MrYuran
Цитата(Dmitro25 @ Mar 31 2010, 14:48) *
Пример:
Код
#pragma vector = TIMER1_COMPA_vect
__interrupt void ext_Timer1_Comp_isr_routine(void)
{
  sd_tick_10ms();
  keydisplay_tick_10ms();
  mdm_tick_10ms();
}

Ну и какая нафиг оптимизация?
Вы 3 раза по 10 мс просто тупо ждёте, и при этом жалеете время на сохранение 16 пар регистров?
cranky.gif

Или я неправильно расшифровал наименование функций?
Dmitro25
2:MALLOY2
Глобальный флаг у меня в другом месте в программе используется. А здесь - вызов нескольких коротких процедур, правда, из разных модулей. Прерывание вызывается каждые 10 мс. А в основной программе, где проверяется глобальный флаг, при некоторых условиях возможна задержка более 10 мс, поэтому желательно эти короткие вызовы оставить всё же в в прерывании.

2MrYuran
Нет, окончание 10мс означает, что функция должна вызываться каждые 10 мс. А сама функция по времени выполнения достаточно быстрая.
XVR
Что бы компилятор понял в процедуре прерывания, что вызываемая оттуда другая процедура не портит регистры, он должен поддерживать IPO (Inter Procedure Optimization - межпроцедурную оптимизацию). Видимо IAR этого не умеет sad.gif gcc это умеет, но не знаю, работает ли это в avr порте gcc
defunct
Цитата(Dmitro25 @ Mar 31 2010, 11:28) *
Задача была вот в чём: создать для каждого периферийного устройства свой модуль в проекте (для инкапсуляции), а по прерываниям в основном модуле проекта вызывать соответствующие функции модулей периферийных устройств.

Для больших проектов, где планируется поддержка разных процов это IMHO единственно верный и прозрачный подход.
Для каждого железа будет просто свой собственный xx_isr.c файл, вместо правки всех периферийных модулей.
Могу посоветовать забить на неоптимальность действий выполняемых компилятором, т.к. потери можно компенсировать повышением частоты.
Тем более для AVR, где уже есть XMEGA с удвоенной частотой.
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.