Как предотвратить GCC от оптимизации цикла ожидания занятости?

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

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

/* 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. Хотя в большинстве случаев было бы лучше использовать эти функции, этот вопрос остается актуальным и интересным.

Ответ 1

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

Документация по атрибутам из 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__(""), если ваш оператор

Ответ 2

Объявить переменные i и j как volatile. Это предотвратит компилятор для оптимизации кода с использованием этих переменных.

unsigned volatile char i, j;

Ответ 3

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

Ответ 4

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

Ответ 5

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

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

Ответ 6

Включение volatile asm должно помочь. Вы можете узнать больше об этом здесь: -

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

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

Ответ 7

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

Что я сделал, так это ссылка на другую пустую функцию, которую компилятор не смог увидеть при компиляции "основной программы"

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

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

void donotoptimize(){}

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

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

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

Я думаю, что он должен работать и с icc. Возможно, нет, если вы включите оптимизацию ссылок, но с gcc он делает.

Ответ 8

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

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

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