Как предотвратить GCC от оптимизации занятого цикла ожидания? - PullRequest
58 голосов
/ 16 августа 2011

Я хочу написать прошивку с кодом C для микроконтроллеров Atmel AVR. Я скомпилирую его с помощью GCC. Кроме того, я хочу включить оптимизацию компилятора (-Os или -O2), так как не вижу причин не включать их, и они, вероятно, будут генерировать лучший способ сборки быстрее, чем запись сборки вручную.

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

/* How to NOT optimize this, while optimizing other code? */
unsigned char i, j;
j = 0;
while(--j) {
    i = 0;
    while(--i);
}

Поскольку доступ к памяти в AVR намного медленнее, я хочу, чтобы i и j сохранялись в регистрах ЦП.


Обновление: я только что нашел util / delay.h и util / delay_basic.h из AVR Libc . Хотя в большинстве случаев лучше использовать эти функции, этот вопрос остается актуальным и интересным.


Смежные вопросы:

Ответы [ 8 ]

73 голосов
/ 16 августа 2011

Я разработал этот ответ после перехода по ссылке из ответа dmckee , но он использует иной подход, чем его / ее ответ.

Атрибуты функций Документация из GCC упоминает:

noinline Этот атрибут функции не позволяет рассматривать функцию для встраивания. Если у функции нет побочных эффектов, есть и другие оптимизации, кроме встраивания, которые вызывают оптимизацию вызовов функций, хотя вызов функции является живым. Чтобы такие звонки не оптимизировались, введите asm ("");

Это дало мне интересную идею ... Вместо добавления инструкции nop во внутренний цикл я попытался добавить туда пустой код сборки, например:

unsigned char i, j;
j = 0;
while(--j) {
    i = 0;
    while(--i)
        asm("");
}

И это сработало! Этот цикл не был оптимизирован, и никакие дополнительные инструкции nop не были вставлены.

Более того, если вы используете volatile, gcc сохранит эти переменные в ОЗУ и добавит набор ldd и std, чтобы скопировать их во временные регистры. Этот подход, с другой стороны, не использует volatile и не генерирует такие издержки.


Обновление: Если вы компилируете код, используя -ansi или -std, вы должны заменить ключевое слово asm на __asm__, как , описанное в документации GCC .

Кроме того, вы также можете использовать __asm__ __volatile__(""), если ваш оператор сборки должен выполняться там, где мы его поместили (т.е. не должен быть перемещен из цикла в качестве оптимизации) .

21 голосов
/ 16 августа 2011

Объявите i и j переменные как volatile.Это не позволит компилятору оптимизировать код, включающий эти переменные.

unsigned volatile char i, j;
5 голосов
/ 02 сентября 2011

Я не уверен, почему еще не было упомянуто, что этот подход полностью ошибочен и легко нарушается при обновлении компилятора и т. Д. Было бы гораздо разумнее определить значение времени, до которого вы хотите подождать, и раскрутитьопрос текущего времени, пока желаемое значение не будет превышено.На x86 вы могли бы использовать rdtsc для этой цели, но более переносимым способом было бы вызвать clock_gettime (или вариант для вашей не-POSIX ОС), чтобы получить время.Текущий x86_64 Linux даже избегает системного вызова для clock_gettime и использует rdtsc для внутреннего использования.Или, если вы можете справиться со стоимостью системного вызова, просто используйте clock_nanosleep, чтобы начать с ...

3 голосов
/ 16 августа 2011

Я не знаю, в полной мере, поддерживает ли avr-версия компилятора полный набор #pragma s (все интересные ссылки приведены в gcc версии 4.4), но это то, с чего вы обычно начинаете.

2 голосов
/ 19 января 2014

Для меня, в GCC 4.7.0, пустой asm все равно был оптимизирован с -O3 (не пытался с -O2).и использование i ++ в регистре или volatile привело к значительному снижению производительности (в моем случае).

То, что я делал, связывалось с другой пустой функцией, которую компилятор не мог видеть при компиляции «основной программы»

В основном это:

Создан "helper.c" с объявленной функцией (пустая функция)

void donotoptimize(){}

Затем скомпилирован "gcc helper.c -c -o helper.o"а затем

while (...) { donotoptimize();}

Это дало мне лучшие результаты (и, по моему мнению, никаких накладных расходов, но не может протестировать, потому что моя программа не будет работать без него :))

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

1 голос
/ 17 августа 2011

Помещение летучего асма должно помочь.Вы можете прочитать больше об этом здесь: -

http://www.nongnu.org/avr-libc/user-manual/optimization.html

Если вы работаете в Windows, вы можете даже попробовать поместить код под прагмы, как подробно объяснено ниже: -

https://www.securecoding.cert.org/confluence/display/cplusplus/MSC06-CPP.+Be+aware+of+compiler+optimization+when+dealing+with+sensitive+data

Надеюсь, это поможет.

1 голос
/ 17 августа 2011

поместите этот цикл в отдельный файл .c и не оптимизируйте этот один файл. Еще лучше написать эту подпрограмму на ассемблере и вызывать ее из C, в любом случае оптимизатор не вмешается.

Я иногда делаю изменчивую вещь, но обычно создаю функцию asm, которая просто возвращает При вызове этой функции оптимизатор сделает цикл for / while плотным, но не оптимизирует его, потому что должен выполнить все вызовы фиктивной функции. Ответ от Денилсона Са на nop делает то же самое, но еще крепче ...

0 голосов
/ 06 сентября 2012

Вы также можете использовать регистр ключевое слово .Переменные, объявленные с помощью регистра, сохраняются в регистрах процессора.

В вашем случае:

register unsigned char i, j;
j = 0;
while(--j) {
    i = 0;
    while(--i);
}
...