Я хочу написать прошивку с кодом 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 . Хотя в большинстве случаев было бы лучше использовать эти функции, этот вопрос остается актуальным и интересным.
Связанные вопросы:
операторы Empty __asm__
недостаточно: лучше используйте зависимости по данным
, Например:
Компиляция main.c
int main(void) {
unsigned i;
for (i = 0; i < 10; i++) {
__asm__ volatile("");
}
}
и демонтируйте:
gcc -O3 -ggdb3 -o main.out main.c
gdb -batch -ex 'disas main' main.out
вывод:
0x0000000000001040 <+0>: xor %eax,%eax
0x0000000000001042 <+2>: retq
Также примечание, которое __asm__("")
и __asm__ volatile("")
должно быть тем же с тех пор, нет никаких выходных операндов: различие между asm, asm энергозависимая и ударяющая память
, Что происходит, становится более ясным, если мы заменяем его:
__asm__ volatile("");
, который производит:
0x0000000000001040 <+0>: nop
0x0000000000001041 <+1>: nop
0x0000000000001042 <+2>: nop
0x0000000000001043 <+3>: nop
0x0000000000001044 <+4>: nop
0x0000000000001045 <+5>: nop
0x0000000000001046 <+6>: nop
0x0000000000001047 <+7>: nop
0x0000000000001048 <+8>: nop
0x0000000000001049 <+9>: nop
0x000000000000104a <+10>: xor %eax,%eax
0x000000000000104c <+12>: retq
, Таким образом, мы видим, что GCC всего цикл развернул nop
цикл в этом случае, потому что цикл был достаточно маленьким.
Так, при использовании этой техники Вы полагались бы трудно для предсказания двоичных компромиссов размера/скорости GCC, которые, если бы применено оптимально, должен всегда удалять цикл для пустого __asm__ volatile("");
, который имеет нуль размера кода.
Одно решение, которому я верю, надежно, должен заменить его явной зависимостью по данным от переменной цикла i
, как предложено в: порядок оператора Enforcing в C++
__asm__ ("" : "+g" (i) : :);
, который производит желаемый цикл:
0x0000000000001040 <+0>: xor %eax,%eax
0x0000000000001042 <+2>: nopw 0x0(%rax,%rax,1)
0x0000000000001048 <+8>: add [116]x1,%eax
0x000000000000104b <+11>: cmp [116]x9,%eax
0x000000000000104e <+14>: jbe 0x1048 <main+8>
0x0000000000001050 <+16>: xor %eax,%eax
0x0000000000001052 <+18>: retq
Это отмечает i
как ввод и вывод встроенного ассемблерного кода. Затем встроенный ассемблерный код является черным квадратом для GCC, который не может знать, как он изменяет i
, таким образом, я думаю, что действительно не может быть оптимизирован далеко.
noinline
занятая функция цикла
, Если размер цикла не известен во время компиляции, полное разворачивание не возможно, но GCC мог все еще решить развернуть в блоках, которые сделают Ваши задержки непоследовательными.
Помещение, что вместе с [1 123] ответ Denilson , я пошел бы для:
void __attribute__ ((noinline)) busy_loop(unsigned max) {
for (unsigned i = 0; i < max; i++) {
__asm__ volatile("" : "+g" (i) : :);
}
}
int main(void) {
busy_loop(10);
}
, который демонтирует в:
Dump of assembler code for function busy_loop:
0x0000000000001140 <+0>: test %edi,%edi
0x0000000000001142 <+2>: je 0x1157 <busy_loop+23>
0x0000000000001144 <+4>: xor %eax,%eax
0x0000000000001146 <+6>: nopw %cs:0x0(%rax,%rax,1)
0x0000000000001150 <+16>: add [118]x1,%eax
0x0000000000001153 <+19>: cmp %eax,%edi
0x0000000000001155 <+21>: ja 0x1150 <busy_loop+16>
0x0000000000001157 <+23>: retq
End of assembler dump.
Dump of assembler code for function main:
0x0000000000001040 <+0>: mov [118]xa,%edi
0x0000000000001045 <+5>: callq 0x1140 <busy_loop>
0x000000000000104a <+10>: xor %eax,%eax
0x000000000000104c <+12>: retq
End of assembler dump.
Здесь эти volatile
, был необходим для маркировки блока как потенциальное наличие побочных эффектов, так как в этом случае у нас есть выходные переменные.
А двойная версия цикла могла быть:
void __attribute__ ((noinline)) busy_loop(unsigned max, unsigned max2) {
for (unsigned i = 0; i < max; i++) {
for (unsigned j = 0; j < max2; j++) {
__asm__ volatile ("" : "+g" (j), "+g" (j) : :);
}
}
}
int main(void) {
busy_loop(10, 10);
}
GitHub в восходящем направлении .
Связанные потоки:
Протестированный в Ubuntu 19.04, GCC 8.3.0.