Может ли atmega328 вернуть остаток при целочисленном делении?

Когда я пишу такой код:

int a = 7
int b = 2
int c = a/b
int r = a%b

Использует ли чип остаток при вычислении c или повторяет весь расчет?

, 👍2

Обсуждение

Atmega328 не имеет встроенной инструкции деления, поэтому она должна быть реализована последовательностью других инструкций. Таким образом, чип даже не знает об инструкции деления., @Kevin White

А, значит, само деление выполняется серией ассемблерных инструкций, а не одной :) Тогда я надеюсь, что компилятор достаточно умен, чтобы сохранить остаток :), @Beacon of Wierd

Почему вы должны знать это? Чтобы разделить на 2, компилятор просто должен сдвинуть вправо на единицу, а остаток — это просто младший бит. Это очень быстрые вещи. «Чип» делает то, что ему говорят., @Nick Gammon

@NickGammon Я решаю дифференциальные уравнения в реальном времени с целыми числами, чтобы предсказать поведение системы, и мне нужно знать, стоит ли отслеживать остатки, чтобы учитывать их или нет, пока это выглядит так, как будто оно того не стоит. хотя, но я хочу изучить свои варианты повышения точности :), @Beacon of Wierd


2 ответа


Лучший ответ:

3

При использовании оптимизации компилятор достаточно умен, чтобы распознать это.

Поскольку ваш пример такой короткий, его можно полностью оптимизировать. Чтобы этого не произошло, необходимо каким-то образом использовать результаты в c и r. Кроме того, если a и b могут никогда измениться, значения c и r будет вычисляться во время компиляции. Поэтому я поместил вычисление в отдельную функцию.

От godbolt.com, используя AVR-GCC 9.2.0:

#include<stdint.h>
#include<stdio.h>

uint8_t a = 0;
uint8_t b = 2;
uint8_t c = 0;
uint8_t r = 0;

void calculate() {
    c = a / b;
    r = a % b;
}

int main() {
    a = 7;
    b = 2;

    calculate();

    printf("%d %d", c, r);
}

компилирует функцию calculate() в

calculate():
        lds r24,a
        lds r22,b
        rcall __udivmodqi4
        sts c,r24
        sts r,r25
        ret

Результаты, сохраненные в r24 и r25 соответственно, повторно используются из той же операции деления.

,

Отключив оптимизацию, вы просите компилятор не быть умным_. И послушно подчиняется. Никто никогда не будет компилировать код Arduino таким образом. Стандартный уровень оптимизации Arduino — «-Os», и на этом уровне компилятор достаточно умен, чтобы оптимизировать ваш код до эквивалента «return 0»., @Edgar Bonet

Ты прав. Хотя даже если убедиться, что результаты используются (даже одним и тем же оператором), а переменные a и b не являются постоянными, кажется, что оставшаяся часть первой операции не запоминается напрямую: https:// godbolt.org/z/Edzq54, @towe

Ваш новый тест снова ошибочен. Имея переменные a и b, вы сообщаете компилятору, что он не должен считать их значения стабильными. Таким образом, «a/b» и «a%b» — это два разных деления., @Edgar Bonet

Ух, я дерьмо в этом. Вы правы, оптимизация использует только одно деление, если вы убедитесь, что переменные не могут измениться, а также запретите ей вычислять все это во время компиляции. https://godbolt.org/z/bG4cG6, @towe

Использование volatile было неплохой идеей: это удобный способ симулировать побочные эффекты, такие как ввод-вывод. Вам просто нужно убедиться, что числа, которые вы делите, сами по себе не являются изменчивыми. См. например: https://godbolt.org/z/zGefj9, @Edgar Bonet


1

Компилятор настолько умен, насколько вы предоставляете ему необходимую информацию.

  1. Хорошо изучить код на сайте godbolt.com. Но используйте его с умом.
    Никто никогда не должен компилировать без оптимизации. В приведенном выше примере код не оптимизирован и поэтому никогда не будет использоваться как таковой на плате.
  2. Пример настолько искусственный, что компилятор оптимизирует его до двух инструкций. Таким образом, код должен быть репрезентативным для предполагаемого использования.
    Лучшим божьим болтом было бы:
#include<stdint.h>
#include<stdlib.h>

void myDiv(uint8_t * c, uint8_t * r, const uint8_t a, const uint8_t b) {
    *c = a/b;
    *r = a%b;
}

Поэтому ввод не предопределен, а также возвращаются результирующие числа, а не отбрасываются. Что переводится с помощью -Os в:

myDiv(unsigned char*, unsigned char*, unsigned char, unsigned char):
        mov r27,r25
        mov r26,r24
        mov r31,r23
        mov r30,r22
        mov r24,r20
        mov r22,r18
        rcall __udivmodqi4
        st X,r24
        st Z,r25
        ret
  1. В stdlib.h есть функция div(числитель, знаменатель). Эта функция выполняет деление и возвращает как делимое, так и остаток. Он оптимизирован для каждой платформы и использует наилучший доступный метод для получения обеих. В Godbolt это выглядело бы так:
#include<stdint.h>
#include<stdlib.h>

void myDiv2(uint8_t * c, uint8_t * r, const uint8_t a, const uint8_t b) {
    div_t d = div(a, b);
    *c = d.quot;
    *r = d.rem;
}

Но что удивительно, с -Os результирующая сборка выглядит точно так же! (Я предоставляю читателю попробовать это на стреле бога).

Это означает, что компилятор достаточно умен, чтобы:

а. распознать намерение при последовательном вызове деления и по модулю одних и тех же операндов.
б. будет использовать наилучший доступный метод для достижения предполагаемого расчета для вас.


EDIT после комментария Эдгара Бонета:
Играясь в godbolt, я сделал ошибку копирования-вставки. div применяется только к значениям int. Это делает результирующий ассемблерный код больше, так как в нем вдвое больше данных, которые нужно перелопатить. После изменения обоих примеров на int они выглядят почти одинаково. Единственное отличие:

Результат сначала сохраняется в d, а затем копируется в c и r.
Таким образом, код с делением по модулю более эффективен в этом отношении, поскольку результирующие значения копируются непосредственно из регистров, используемых в соглашении о вызовах.

https://godbolt.org/z/61exz7

,

Очень интересно, +1 Объявление оптимизации в моем коде избавляет почти от всего кода, но объявление переменных как volatile предотвращает это. Даже при «волатильности» она все равно не признает потенциальной экономии как таковой и рассчитывает деление дважды., @towe

если вы внимательно прочитаете ассемблерный код, то увидите, что это не так., @Kwasmich

Извините, я все еще говорил о своем (близком к вопросу) коде: https://godbolt.org/z/jWqdv4, @towe

Я рассматриваю рассматриваемый код как образец, который показывает намерение, а не вопрос, на который нужно ответить. Создание всего изменчивого запрещает оптимизацию компилятором. Так что нет смысла делать так и иметь -Os. Если мы будем придерживаться точного кода, о котором идет речь, то результатом будут всего две инструкции, потому что входные данные известны во время компиляции, они не меняются, а результаты вычислений отбрасываются., @Kwasmich

+1. Во втором примере вы имеете в виду *c = d.quot; *r = d.rem;., @Edgar Bonet

@EdgarBonet спасибо, что указали на это., @Kwasmich