Типы данных и аргументы
Микроконтроллер stm32f103c8 имеет 32-битное ядро Cortex-M3 (ARMv7-M) и, следовательно, наиболее эффективно работает с 32-битными данными. Другими словами, лучше избегать char
и short
в качестве локальных переменных. Исключением будут ситуации, когда программист сознательно использует такое свойство типа данных. Например, 8-битный тип данных char при переходе через 255
даст 0
.
Рассмотрим следующий код:
for (char i = 0; i < 127; i++) {}
На первый взгляд может показаться, что использование char более выгодно, так как он займет меньше места в памяти или в регистре процессора, чем обычный int
. Это предположение ошибочно: все регистры являются 32-битными, в стеке размерность данных тоже 32 бита. Посмотрим вывод ассемблерного кода (без оптимизации):
MOVS R0, #0
B.N 0x80000f4
ADDS R0, R0, #1
UXTB R0, R0
CMP R0, #127
BLT.N 0x80000f2
И точно такой же код, но с использованием 32-битной переменной (uint32_t
):
MOVS R0, #0
B.N 0x8000100
ADDS R0, R0, #1
CMP R0, #127
BCC.N 0x80000fe
Вдаваться в смысл каждой инструкции не нужно, здесь важно их количество: при использовании 8-битной переменной компилятор генерирует на одну строчку больше. По сути UXTB R0, R0
эквивалентна записи:
i = (char) i;
Как вы понимаете, программа совершит на 127 операций больше, чем при использовании 32-битной переменной. При использовании оптимизации (ключа) компилятор самостоятельно изменяет тип переменной, увеличивая тем самым производительность.
Аналогичная проблема возникает и с аргументами функций. Дело в том, что сами аргументы передаются в регистры ядра, о которых мы говорили ранее, — r0
, r1
, r2
, r3
, а они являются 32-битными. Допустим, у нас есть некая функция:
short vars_in_func_1(short a, short b) {
return a + (b >> 1);
}
Аргументы складываются в 32-битные регистры, при этом программист сам не следит за тем, чтобы возвращаемое значение находилось в пределах [-32768, +32767]. Этим снова займется компилятор, добавив лишнюю инструкцию.
В операциях сложения, вычитания и умножения разницы в производительности знаковых и беззнаковых переменных нет. Разница появляется при делении.
int mean_signed(int a, int b) {
return (a + b) / 2;
}
Компилятор добавляет единицу к сумме перед смещением в том случае, если сумма отрицательна. Например, x / 2
заменяется на выражение:
(x < 0) ? ((x + 1) >> 1) : (x >> 1)
Это происходит потому, что переменная x знаковая. Например, -3 >> 1 = -2
, но -3 / 2 = -1
. Более того, в Cortex-M3 присутствует инструкция деления (SDIV
для знакового и UDIV
для беззнакового), в то время как в Cortex-M0, например, ее нет. Замерить производительность вы можете, используя системный таймер SysTick.
Хорошим тоном в программировании встраиваемых систем является точное указание размера (т.е. и диапазона) переменной. В этой связи unsigned int
лучше заменять на uint32_t
. При переносе кода, скажем, на 8-битный микроконтроллер код продолжает исправно работать, так как uint32_t
— аппаратнонезависимый тип. Если есть необходимость писать код для различных микроконтроллеров, то более эффективным будет использование типов с суффиксом _fast
. Фактическая разрядность переменной типа uint_fast16_t
различается для различных контроллеров, но всегда не меньше 16 бит. Например, для 32-битных ARM это будет 32-разрядная целочисленная переменная, а для AVR — 16-разрядная.
>Вопрос 48. Какой тип целочисленной переменной лучше всего СПОЛЬЗОВАТЬ в микроконтроллере PIC24 и почему?
Здравствуйте.
Мучаю STM32F030F4P6 и изучаю вашу книгу.
Замерял сегодня скорость выполнения вышеприведённых циклов на разных оптимизациях кода O1 O2 O3.
И у меня получилось что цикл
for (i = 0; i < 1000000; i++) {} выполняется на любой оптимизации за 4800 микросекунд
цикл for ( i = 1000000; i != 0; i—){} выполняется за 4800 микросек. только на оптимизации O1 . При оптимизации O2 и O3 он выполняется за 10800 микросекунд.
цикл
n = 1000000;
do {
// code here
} while (—n != 0); на любой оптимизации выполняется за 10800 микросекунд
может это связано с компиляцией и оптимизацией именно для данного чипа.
но лучше я буду писать for (i = 0; i < 1000000; i++){}
Взял осциллограф и всё перепроверил. Оказалось что у меня таймер переполнялся.
Думаю что мои коменты публиковать не нужно )