Сравнение эффективности компиляторов для Atmel AVR

в 6:32, , рубрики: arduino, avr, bascom, Программинг микроконтроллеров, метки: , , , ,

В статье habrahabr.ru/post/151544/ и комментариях к ней возник вопрос, почему разные компиляторы выдают код не только разного размера, но и разной эффективности. Если с первым фактором еще можно мириться, то второй может свести все ваши усилия в написании программы на нет. Это на PC еще можно добавить памяти до 32 Гб и поставить 16 ядерный процессор на 10 ГГц, чтобы косынка на NET8.0 не тормозила, а embedded система — девица более изысканная и утонченная.
Итак, сравниваем компиляторы:
1. BASCOM-AVR
2. Wiring
3. С CodeVisionAVR.

Прошивки, любезно предоставленные для исследования товарищем vvzvlad, дизассемблируем с помощью инструмента всех времен и народов IDA. Исходные коды приведены в комментарии habrahabr.ru/post/151544/#comment_5141741.
В результате изучения ассемблерных «исходников» можно отметить, что BASCOM и С CVAVR делают практически одинаковый и эффективный код, ардуиновский Wiring по сравнению с ними ловит тунцов. :)

Аперитив

Коды инициализации особого интереса не представляют, но они есть. BASCOM и C используют только то, что требуется программе, Wiring имеет чуть более чем больший инициализационный код, а также обрабатывает прерывание по таймеру Timer0, хотя в программе он нам не нужен.
К примеру, инициализация по версии BASCOM:

ROM:0034 __RESET:                                ; CODE XREF: ROM:0000
ROM:0034                 ser     r24
ROM:0035                 out     SPL, r24
ROM:0036                 ldi     r28, -0x20 ; 'р'
ROM:0037                 ldi     r30, -0x38 ; 'L'
ROM:0038                 mov     r4, r30
ROM:0039                 ldi     r24, 4
ROM:003A                 out     SPH, r24
ROM:003B                 ldi     r29, 4
ROM:003C                 ldi     r31, 4
ROM:003D                 mov     r5, r31
ROM:003E                 wdr
ROM:003F                 in      r24, MCUSR
ROM:0040                 mov     r0, r24
ROM:0041                 andi    r24, -9
ROM:0042                 out     MCUSR, r24
ROM:0043                 ldi     r24, 0x18
ROM:0044                 clr     r25
ROM:0045                 sts     WDTCSR, r24
ROM:0047                 sts     WDTCSR, r25
ROM:0049                 ldi     r30, -2 ; '¦'
ROM:004A                 ldi     r31, 3
ROM:004B                 ldi     r26, 0
ROM:004C                 ldi     r27, 1
ROM:004D                 clr     r24
ROM:004E
ROM:004E loc_4E:                                 ; CODE XREF: __RESET+1C
ROM:004E                 st      X+, r24
ROM:004F                 sbiw    r30, 1
ROM:0050                 brne    loc_4E
ROM:0051                 clr     r6
ROM:0052                 sbi     DDRB, DDB5

Cи CVAVR хочет от мелкоконтроллера почти того же самого, но кода побольше:

ROM:0034 __RESET:                                ; CODE XREF:
ROM:0034                 cli
ROM:0035                 clr     r30
ROM:0036                 out     EECR, r30
ROM:0037                 ldi     r31, 1
ROM:0038                 out     MCUCR, r31
ROM:0039                 out     MCUCR, r30
ROM:003A                 ldi     r31, 0x18
ROM:003B                 wdr
ROM:003C                 in      r26, MCUSR
ROM:003D                 andi    r26, -9
ROM:003E                 out     MCUSR, r26
ROM:003F                 sts     WDTCSR, r31
ROM:0041                 sts     WDTCSR, r30
ROM:0043                 ldi     r24, 0xD
ROM:0044                 ldi     r26, 2
ROM:0045                 clr     r27
ROM:0046
ROM:0046 loc_46:                                 ; CODE XREF: __RESET+14
ROM:0046                 st      X+, r30
ROM:0047                 dec     r24
ROM:0048                 brne    loc_46
ROM:0049                 ldi     r24, 0
ROM:004A                 ldi     r25, 4
ROM:004B                 ldi     r26, 0
ROM:004C                 ldi     r27, 1
ROM:004D
ROM:004D loc_4D:                                 ; CODE XREF: __RESET+1B
ROM:004D                 st      X+, r30
ROM:004E                 sbiw    r24, 1
ROM:004F                 brne    loc_4D
ROM:0050                 ldi     r30, 0
ROM:0051                 out     GPIOR0, r30
ROM:0052                 ser     r30
ROM:0053                 out     SPL, r30
ROM:0054                 ldi     r30, 4
ROM:0055                 out     SPH, r30
ROM:0056                 ldi     r28, 0
ROM:0057                 ldi     r29, 2
ROM:0058                 jmp     loc_5A
ROM:005A
ROM:005A loc_5A:
ROM:005A                 ldi     r30, -0x80 ; 'А'
ROM:005B                 sts     CLKPR, r30
ROM:005D                 ldi     r30, 0
ROM:005E                 sts     CLKPR, r30
ROM:0060                 ldi     r30, 0x20 ; ' '
ROM:0061                 out     DDRB, r30

Инициализация Wiring №7:

ROM:0061 __RESET:                                ; CODE XREF: j___RESET
ROM:0061
ROM:0061 ; FUNCTION CHUNK AT ROM:015B SIZE 00000002 BYTES
ROM:0061
ROM:0061                 clr     r1
ROM:0062                 out     SREG, r1
ROM:0063                 ser     r28
ROM:0064                 ldi     r29, 4
ROM:0065                 out     SPH, r29
ROM:0066                 out     SPL, r28
ROM:0067                 ldi     r17, 1
ROM:0068                 ldi     r26, 0
ROM:0069                 ldi     r27, 1
ROM:006A                 ldi     r30, -0x46 ; '¦'
ROM:006B                 ldi     r31, 2
ROM:006C                 rjmp    loc_6F
ROM:006D ; ---------------------------------------------------------------------------
ROM:006D
ROM:006D loc_6D:                                 ; CODE XREF: __RESET
ROM:006D                 lpm     r0, Z+
ROM:006E                 st      X+, r0
ROM:006F
ROM:006F loc_6F:                                 ; CODE XREF: __RESET
ROM:006F                 cpi     r26, 0
ROM:0070                 cpc     r27, r17
ROM:0071                 brne    loc_6D
ROM:0072                 ldi     r17, 1
ROM:0073                 ldi     r26, 0
ROM:0074                 ldi     r27, 1
ROM:0075                 rjmp    loc_77
ROM:0076 ; ---------------------------------------------------------------------------
ROM:0076
ROM:0076 loc_76:                                 ; CODE XREF: __RESET+18
ROM:0076                 st      X+, r1
ROM:0077
ROM:0077 loc_77:                                 ; CODE XREF: __RESET+14
ROM:0077                 cpi     r26, 9
ROM:0078                 cpc     r27, r17
ROM:0079                 brne    loc_76
ROM:007A                 call    sub_C9
ROM:007C                 jmp     loc_15B

За строкой «007A call sub_C9» притаились вложенные функции, которые что-то зачем-то инициализируют ;)
Но, собственно, какие проблемы, стартовать системе 1 мс или 10 мс или даже 100 мс? Подробные подробности следующим письмом оставим на закуску.

Основное блюдо

Что делает с полезным кодом компилятор BASCOM:
1.hex

Do
 Portb = 0
 Portb = 255
Loop

Превращается в:

ROM:0053 loc_53:                                 ; CODE XREF: __RESET+23
ROM:0053                 ldi     r23, 0
ROM:0054                 out     PORTB, r23
ROM:0055                 ser     r23
ROM:0056                 out     PORTB, r23
ROM:0057                 jmp     loc_53

Просто, надежно, эффективно.
И программа:
2.hex

Do
Toggle Portb.5
Loop

Превратилась в:

ROM:0053 loc_53:                                 ; CODE XREF: __RESET+21
ROM:0053                 ldi     r24, 0x20 ; ' '
ROM:0054                 out     PINB, r24
ROM:0055                 jmp     loc_53

Фокус с постоянным выводом «1» в порт возможен благодаря особенностям архитектуры (чудо просто эти AVR). Чистый ассемблер и никакого мошенничества.
Cи CVAVR сделал то, что ему сказали и ни грамма байта больше:
7.hex

while (1)
{
PORTB.5=1;
PORTB.5=0;
};
}

Ассемблерный код:

ROM:0062 loc_62:                                 ; CODE XREF: __RESET+30
ROM:0062                 sbi     PORTB, PORTB5
ROM:0063                 cbi     PORTB, PORTB5
ROM:0064                 rjmp    loc_62

У кода Arduino найти полезный участок займет не одну минуту, я справился с этой задачей загнав прошивку в симулятор протеуса.
3.hex

void loop() {
digitalWrite(13, HIGH);
digitalWrite(13, LOW); 
}

Вот он, вызов функции digitalWrite:

ROM:0080 sub_80:                                 ; CODE XREF: sub_121:loc_129
ROM:0080                 ldi     r24, 0xD
ROM:0081                 ldi     r22, 1
ROM:0082                 call    sub_CD          ; HERE<<<<
ROM:0084                 ldi     r24, 0xD
ROM:0085                 ldi     r22, 0
ROM:0086                 call    sub_CD          ; HERE<<<<
ROM:0088                 ret

Саму функцию можно представить в виде следующей картинки (примите извинения за радикал, уважаемые читатели):
image
Кода много, код ветвист и изобилует коллами и джампами, смотрите его лучше сразу в дизассемблере.
Следующий вариант удачнее:
4.hex

void loop() {
PORTB ^= 1<<5;
}

Что мы имеем с этого супа:
Главный цикл:

ROM:00D1 loc_D1:                                 ; CODE XREF: sub_C9
ROM:00D1                                         ; sub_C9
ROM:00D1                 call    sub_80
ROM:00D3                 sbiw    r28, 0
ROM:00D4                 breq    loc_D1
ROM:00D5                 call    j___RESET
ROM:00D7                 rjmp    loc_D1

И функция вывода:

ROM:0080 sub_80:                                 ; CODE XREF: sub_C9:loc_D1
ROM:0080                 in      r24, PORTB
ROM:0081                 ldi     r25, 0x20 ; ' '
ROM:0082                 eor     r24, r25
ROM:0083                 out     PORTB, r24
ROM:0084                 ret

Следующий! (с) анекдот
5.hex

void loop() {
PORTB=1;
PORTB=255;
}

Напомнило первый случай BASCOM с точностью до бита:

ROM:0080 sub_80:                                 ; CODE XREF: sub_C9:loc_D1
ROM:0080                 ldi     r24, 1
ROM:0081                 out     PORTB, r24
ROM:0082                 ser     r24
ROM:0083                 out     PORTB, r24
ROM:0084                 ret

А вызывается оно вот отсюда, из главного цикла:

ROM:00D1 loc_D1:                                 ; CODE XREF: sub_C9
ROM:00D1                                         ; sub_C9+E
ROM:00D1                 call    sub_80
ROM:00D3                 sbiw    r28, 0
ROM:00D4                 breq    loc_D1
ROM:00D5                 call    j___RESET
ROM:00D7                 rjmp    loc_D1

Еще парочку! (с) Булгаков
Почти то же, что и в прошивке номер 5:
6.hex

void loop() {
  while(1){
PORTB=1;
PORTB=255;
}
}

И результат прогнозируем:

ROM:0080 sub_80:                                 ; CODE XREF: sub_C9:loc_D1
ROM:0080                 ldi     r25, 1
ROM:0081                 ser     r24
ROM:0082
ROM:0082 loc_82:                                 ; CODE XREF: sub_80
ROM:0082                 out     PORTB, r25
ROM:0083                 out     PORTB, r24
ROM:0084                 rjmp    loc_82
Второе блюдо

Почему мне не понравился Wiring? Ладно, вы уговорили меня не использовать тяжелые библиотечные функции, будем писать все на ассемблере напрямую в порты и регистры, и куда теперь идут все наработанные сообществом библиотеки?
А вот эта фраза: «Управдом друг человека!» «ROM:0061; FUNCTION CHUNK AT ROM:01B3 SIZE 00000002 BYTES» А означает она, что выполняли мы код, а потом неожиданно свалили по джампу в другое место. Ради одной команды, между прочим.

ROM:015B loc_15B:                                ; CODE XREF: __RESET+1B
ROM:015B                 cli
ROM:015C
ROM:015C loc_15C:                                ; CODE XREF: __RESET:loc_15C
ROM:015C                 rjmp    loc_15C

Wiring тут не одинок, видел я проекты на Сях и под AVR и под PIC, где функция разбита на кучу кусочков, распиханных в разные места памяти программ и связана кучей джампов (+2 такта на выполнение) и коллов (+1 место в стеке, + 4 такта на AVR). Переходим к тому кусочку — а нам затычка в виде ret ( + 4 такта на AVR) :), ибо компилятор так посчитал нужным. А вдруг возникает желание отладить не на языке высокого уровня, а в железе, потрогать, так сказать, его за регистры. И летает программа туда-сюда, с нулевого адреса на сотый и обратно, готовый антихакинг антиреверсинг… Эффективность? 640 кБ хватит всем ;)

Обещанная закуска

А на закуску я взял протеус, 7 чипов, 7 прошивок, запустил и сравнил. Сверху вниз 1, 2, 3, 4, 5, 6 и, конечно же, 7.
Время старта (инициализация переда началом выполнения полезной функиции):
image
А в победителях у нас оказался....Wiring! Значит функция инициализации тут не такая уж и навороченная оказалась ;) Фавориты BASCOM и CVAVR идут ноздря в ноздрю, но на почетном втором месте.
Ну и время выполнения главной задачи — пульсирующий выход.
image
Впереди бесспорно CVAVR, на втором месте Wiring v.6, 3 и 4 места заслуженно у BASCOMа.

Подведем итоги. Что же выбрать в качестве языка? Если вы хотите использовать железо на 101% и не тратить впустую его ресурсы — ассемблер ваше все. Если вы доверяете трату ресурсов авторам компиляторов, а писать хотите быстро и эффективно — берите С или даже BASCOM. Если хотите создать код быстро, ресурсов у вас много, а задача состоит помигать светодиодом, нет острой необходимости в пошаговой отладке кода в железе, а также очень желательно защитить код от реверсинжиниринга и не давать никому исходников — используйте Wiring, библиотечные оптимизированные функции и прочие Це два плюса.
Всем пис :)

Автор: Int_13h

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js