Может ли atmega328 вернуть остаток при целочисленном делении?
Когда я пишу такой код:
int a = 7
int b = 2
int c = a/b
int r = a%b
Использует ли чип остаток при вычислении c или повторяет весь расчет?
@Beacon of Wierd, 👍2
Обсуждение2 ответа
Лучший ответ:
При использовании оптимизации компилятор достаточно умен, чтобы распознать это.
Поскольку ваш пример такой короткий, его можно полностью оптимизировать.
Чтобы этого не произошло, необходимо каким-то образом использовать результаты в 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
Компилятор настолько умен, насколько вы предоставляете ему необходимую информацию.
- Хорошо изучить код на сайте godbolt.com. Но используйте его с умом.
Никто никогда не должен компилировать без оптимизации. В приведенном выше примере код не оптимизирован и поэтому никогда не будет использоваться как таковой на плате. - Пример настолько искусственный, что компилятор оптимизирует его до двух инструкций. Таким образом, код должен быть репрезентативным для предполагаемого использования.
Лучшим божьим болтом было бы:
#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
- В
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
- Можем ли мы записать загрузчик Arduino в любой чип микроконтроллера?
- Как подключить USB к пользовательской схеме Arduino Uno и программированию Atmega?
- Программирование ATMega328P без платы Arduino всегда возвращает ошибку: avrdude: stk500_recv(): programmer is not responding
- Использование Arduino Nano для программирования (как ISP) автономного 328p
- Arduino вылетает и перезагружается
- 24/14 секундный таймер обратного отсчета или часы для кнопки запуска/остановки баскетбольного мяча плохо реагируют
- Загрузка на внешний ATmega328P с клоном UNO R3 с припаянной микросхемой
- Возможно сломал новый atmega328p?
Atmega328 не имеет встроенной инструкции деления, поэтому она должна быть реализована последовательностью других инструкций. Таким образом, чип даже не знает об инструкции деления., @Kevin White
А, значит, само деление выполняется серией ассемблерных инструкций, а не одной :) Тогда я надеюсь, что компилятор достаточно умен, чтобы сохранить остаток :), @Beacon of Wierd
Почему вы должны знать это? Чтобы разделить на 2, компилятор просто должен сдвинуть вправо на единицу, а остаток — это просто младший бит. Это очень быстрые вещи. «Чип» делает то, что ему говорят., @Nick Gammon
@NickGammon Я решаю дифференциальные уравнения в реальном времени с целыми числами, чтобы предсказать поведение системы, и мне нужно знать, стоит ли отслеживать остатки, чтобы учитывать их или нет, пока это выглядит так, как будто оно того не стоит. хотя, но я хочу изучить свои варианты повышения точности :), @Beacon of Wierd