Переписываем циклы

Написать более грамотный код можно и в циклах. Стандартная запись с известным числом итераций имеет вид:

uint32_t i = 0;
for (i = 0; i < 127; i++) {}

Очевидно, что для реализации самого цикла на языке ассемблера потребуются следующие три инструкции:

  • увеличение i на 1 (инструкция ADD);
  • сравнение i с пороговым значением (127, инструкция CMP);
  • и инструкция продолжения цикла, если i < 127 (BCC.N).

Компилятор выведет примерно следующее:

B.N       0x80001f4
ADDS      R0, R0, #1
CMP       R0, #127
BCC.N     0x80001f2

Однако особенность ARM позволяет реализовать цикл, используя всего две инструкции:

  • уменьшение i на 1, которая в то же время будет являться условием цикла;
  • инструкция продолжения цикла, если i != 0.

То есть переписать цикл стоит следующим образом:

for (i = 127; i != 0; i--) {}

В таком случае транслятор выдаст следующий ассемблерный код:

B.N       0x8000222
SUBS      R1, R1, #1
NE.N      0x8000220

Таким образом можно сэкономить дополнительные 127 операций.

Если число итераций заранее не известно, то поступать стоит следующим образом:

uint32_t n = 127;
for (; n != 0; n--) {
    // code here
}

или прибегнуть к конструкции dowhile.

n = 127;
do {
    // code  here
} while (--n != 0);

Если использовать for, проверка n производится перед выполнением тела цикла, а значит, записав цикл через dowhile, можно исключить одну лишнюю операцию.

Все эти тонкости зачастую решаются компилятором. Также он способен совершать размотку цикла. Данный метод мы рассматривать не будем.


Изменено:

Эффективный код для Cortex-M: 3 комментария

  1. >Вопрос 48. Какой тип целочисленной переменной лучше всего СПОЛЬЗОВАТЬ в микроконтроллере PIC24 и почему?

  2. Здравствуйте.
    Мучаю 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++){}

  3. Взял осциллограф и всё перепроверил. Оказалось что у меня таймер переполнялся.
    Думаю что мои коменты публиковать не нужно )

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.