Сравнил для интереса на небольшом фрагменте кода эффективность двух трансляторов под ARM (конкретнее, под ядро Cortex-M3): GCC 4.5-4.6 и KEIL 4.21 (последний, кто не в курсе, является "официальным" компилятором и средой разработки: фирму KEIL во время оно купила ARM). Исходник такой:
Код:
void CPU_Init(void)
{
RCC->CR |= RCC_CR_HSEON | RCC_CR_HSION;
RCC->AHBENR = AHB_CLOCK_ENABLE;
RCC->APB1ENR = APB1_CLOCK_ENABLE;
RCC->APB2ENR = APB2_CLOCK_ENABLE;
while ( (RCC->CR & RCC_CR_HSIRDY) == 0 ) ;
RCC->CFGR = RCC_CFGR_MCO_NOCLOCK |
RCC_CFGR_USBPRE_1_5 |
RCC_CFGR_PLLMUL9 |
RCC_CFGR_PLLXTPRE_HSE |
RCC_CFGR_PLLSRC_HSE |
RCC_CFGR_ADCPRE_DIV6 |
RCC_CFGR_PPRE2_DIV1 |
RCC_CFGR_PPRE1_DIV2 |
RCC_CFGR_HPRE_DIV1 |
RCC_CFGR_SW_HSI;
while ( (RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_HSI ) ;
FLASH->ACR = FLASH_ACR_LATENCY_2 | FLASH_ACR_PRFTBE;
while ( (RCC->CR & RCC_CR_HSERDY) == 0 ) ;
RCC->CR |= RCC_CR_PLLON;
while ( (RCC->CR & RCC_CR_PLLRDY) == 0 ) ;
RCC->CFGR = RCC_CFGR_MCO_NOCLOCK |
RCC_CFGR_USBPRE_1_5 |
RCC_CFGR_PLLMUL9 |
RCC_CFGR_PLLXTPRE_HSE |
RCC_CFGR_PLLSRC_HSE |
RCC_CFGR_ADCPRE_DIV6 |
RCC_CFGR_PPRE2_DIV1 |
RCC_CFGR_PPRE1_DIV2 |
RCC_CFGR_HPRE_DIV1 |
RCC_CFGR_SW_PLL;
while ( (RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL ) ;
GPIOA->CRH = GPIOA_CRH;
GPIOA->CRL = GPIOA_CRL;
GPIOB->CRH = GPIOB_CRH;
GPIOB->CRL = GPIOB_CRL;
GPIOC->CRH = GPIOC_CRH;
GPIOC->CRL = GPIOC_CRL;
GPIOD->CRL = GPIOD_CRL;
AFIO->MAPR = AFIO_MAPR;
}
Все переменные -- это "неустойчивые" (volatile) структуры с фиксированными адресами, которые являются на самом деле блоками регистров микроконтроллеров семейства STM32F10xxx. Объединение регистров устройств в структуры объясняется особенностью архитектуры ARM: у неё нет непосредственной адресации памяти (чего-то типа MOV [адрес], EAX для IA-32), и любое обращение к памяти выполняется с использованием базового адреса в регистре (например, STR R1, [R5, #12] -- записать содержимое регистра 1 в память по адресу, равному сумме регистра 5 и константы 12).
В обоих случаях использовалась оптимизация -O2 (означающая одно и то же: почти максимальную оптимизацию, за исключением, главным образом, инлайнов; -О3 в данном случае даёт такой же результат).
KEIL выдал такой код:
Код:
KERNEL:00000000 KERNEL_0 ; Alternative name is 'CPU_Init'
KERNEL:00000000 LDR R0, =0x40021000
KERNEL:00000002 LDR R1, [R0]
KERNEL:00000004 ORR.W R1, R1, #0x10001
KERNEL:00000008 STR R1, [R0]
KERNEL:0000000A MOVS R1, #0x15
KERNEL:0000000C STR R1, [R0,#0x14]
KERNEL:0000000E MOVS R1, #0
KERNEL:00000010 STR R1, [R0,#0x1C]
KERNEL:00000012 MOVW R1, #0x403D
KERNEL:00000016 STR R1, [R0,#0x18]
KERNEL:00000018
KERNEL:00000018 loc_18 ; CODE XREF: KERNEL:0000001Cj
KERNEL:00000018 LDR R1, [R0]
KERNEL:0000001A LSLS R1, R1, #0x1E
KERNEL:0000001C BPL loc_18
KERNEL:0000001E LDR R1, =0x1D8400
KERNEL:00000020 STR R1, [R0,#4]
KERNEL:00000022
KERNEL:00000022 loc_22 ; CODE XREF: KERNEL:00000028j
KERNEL:00000022 LDR R1, [R0,#4]
KERNEL:00000024 TST.W R1, #0xC
KERNEL:00000028 BNE loc_22
KERNEL:0000002A LDR R2, =0x40022000
KERNEL:0000002C MOVS R1, #0x12
KERNEL:0000002E STR R1, [R2]
KERNEL:00000030
KERNEL:00000030 loc_30 ; CODE XREF: KERNEL:00000034j
KERNEL:00000030 LDR R1, [R0]
KERNEL:00000032 LSLS R1, R1, #0xE
KERNEL:00000034 BPL loc_30
KERNEL:00000036 LDR R1, [R0]
KERNEL:00000038 ORR.W R1, R1, #0x1000000
KERNEL:0000003C STR R1, [R0]
KERNEL:0000003E
KERNEL:0000003E loc_3E ; CODE XREF: KERNEL:00000042j
KERNEL:0000003E LDR R1, [R0]
KERNEL:00000040 LSLS R1, R1, #6
KERNEL:00000042 BPL loc_3E
KERNEL:00000044 LDR R1, =0x1D8400
KERNEL:00000046 ADDS R1, R1, #2
KERNEL:00000048 STR R1, [R0,#4]
KERNEL:0000004A
KERNEL:0000004A loc_4A ; CODE XREF: KERNEL:00000052j
KERNEL:0000004A LDR R1, [R0,#4]
KERNEL:0000004C UBFX.W R1, R1, #2, #2
KERNEL:00000050 CMP R1, #2
KERNEL:00000052 BNE loc_4A
KERNEL:00000054 LDR R1, =0x40010804
KERNEL:00000056 LDR R0, =0x444444B4
KERNEL:00000058 STR R0, [R1]
KERNEL:0000005A SUBS R1, R1, #4
KERNEL:0000005C LDR R0, =0xB4B44404
KERNEL:0000005E STR R0, [R1]
KERNEL:00000060 LDR R1, =0x40010C04
KERNEL:00000062 MOV.W R0, #0x33333333
KERNEL:00000066 STR R0, [R1]
KERNEL:00000068 SUBS R1, R1, #4
KERNEL:0000006A MOV.W R0, #0x44444444
KERNEL:0000006E STR R0, [R1]
KERNEL:00000070 LDR R1, =0x40011000
KERNEL:00000072 STR R0, [R1,#4]
KERNEL:00000074 STR R0, [R1]
KERNEL:00000076 LDR R1, =0x40011400
KERNEL:00000078 STR R0, [R1]
KERNEL:0000007A LDR R1, =0x40010000
KERNEL:0000007C MOV.W R0, #0x4000
KERNEL:00000080 STR R0, [R1,#4]
KERNEL:00000082 BX LR
KERNEL:00000084
А у GCC получилось следующее:
Код:
.text:00000000 cpu__cpu_init
.text:00000000 PUSH {R4-R6}
.text:00000002 MOV R3, 0x40021000
.text:0000000A LDR R2, [R3]
.text:0000000C MOV R1, R3
.text:0000000E ORR.W R2, R2, #0x10001
.text:00000012 STR R2, [R3]
.text:00000014 MOVS R2, #0x15
.text:00000016 STR R2, [R3,#0x14]
.text:00000018 MOVS R2, #0
.text:0000001A STR R2, [R3,#0x1C]
.text:0000001C MOVW R2, #0x403D
.text:00000020 STR R2, [R3,#0x18]
.text:00000022
.text:00000022 loc_22 ; CODE XREF: cpu__cpu_init+30j
.text:00000022 LDR R2, [R1]
.text:00000024 MOV.W R3, #0x1000
.text:00000028 TST.W R2, #2
.text:0000002C MOVT.W R3, #0x4002
.text:00000030 BEQ loc_22
.text:00000032 LDR R0, =unk_114
.text:00000034 MOV R2, R3
.text:00000036 LDR R1, [R0]
.text:00000038 STR R1, [R3,#4]
.text:0000003A
.text:0000003A loc_3A ; CODE XREF: cpu__cpu_init+42j
.text:0000003A LDR R3, [R2,#4]
.text:0000003C UBFX.W R3, R3, #2, #2
.text:00000040 CMP R3, #0
.text:00000042 BNE loc_3A
.text:00000044 MOVS R1, #2
.text:00000046 MOVS R2, #0
.text:00000048 BFI.W R2, R1, #0, #3
.text:0000004C BFI.W R2, R3, #3, #1
.text:00000050 ORR.W R2, R2, #0x10
.text:00000054 MOV.W R12, #0x2000
.text:00000058 BFI.W R2, R3, #5, #1
.text:0000005C MOVT.W R12, #0x4002
.text:00000060 MOV.W R1, #0x1000
.text:00000064 STR.W R2, [R12]
.text:00000068 MOVT.W R1, #0x4002
.text:0000006C
.text:0000006C loc_6C ; CODE XREF: cpu__cpu_init+7Aj
.text:0000006C LDR R2, [R1]
.text:0000006E MOV.W R3, #0x1000
.text:00000072 TST.W R2, #0x20000
.text:00000076 MOVT.W R3, #0x4002
.text:0000007A BEQ loc_6C
.text:0000007C LDR R2, [R3]
.text:0000007E MOV R1, R3
.text:00000080 ORR.W R2, R2, #0x1000000
.text:00000084 STR R2, [R3]
.text:00000086
.text:00000086 loc_86 ; CODE XREF: cpu__cpu_init+94j
.text:00000086 LDR R2, [R1]
.text:00000088 MOV.W R3, #0x1000
.text:0000008C TST.W R2, #0x2000000
.text:00000090 MOVT.W R3, #0x4002
.text:00000094 BEQ loc_86
.text:00000096 LDR R1, [R0,#4]
.text:00000098 MOV R2, R3
.text:0000009A STR R1, [R3,#4]
.text:0000009C
.text:0000009C loc_9C ; CODE XREF: cpu__cpu_init+A4j
.text:0000009C LDR R3, [R2,#4]
.text:0000009E UBFX.W R3, R3, #2, #2
.text:000000A2 CMP R3, #2
.text:000000A4 BNE loc_9C
.text:000000A6 MOV.W R12, #0x800
.text:000000AA MOVW R6, #0x44B4
.text:000000AE MOVW R5, #0x4404
.text:000000B2 MOVT.W R12, #0x4001
.text:000000B6 MOV.W R0, #0xC00
.text:000000BA MOV.W R1, #0x1000
.text:000000BE MOV.W R4, #0x1400
.text:000000C2 MOVT.W R6, #0x4444
.text:000000C6 MOVT.W R5, #0xB4B4
.text:000000CA STR.W R6, [R12,#4]
.text:000000CE MOV.W R2, #0x44444444
.text:000000D2 STR.W R5, [R12]
.text:000000D6 MOVT.W R0, #0x4001
.text:000000DA MOVT.W R1, #0x4001
.text:000000DE MOVS R3, #0
.text:000000E0 MOVT.W R4, #0x4001
.text:000000E4 MOV.W R12, #0x33333333
.text:000000E8 STR.W R12, [R0,#4]
.text:000000EC MOVT.W R3, #0x4001
.text:000000F0 STR R2, [R0]
.text:000000F2 STR R2, [R1,#4]
.text:000000F4 STR R2, [R1]
.text:000000F6 STR R2, [R4]
.text:000000F8 MOV.W R2, #0x4000
.text:000000FC STR R2, [R3,#4]
.text:000000FE LDR R2, [R3,#4]
.text:00000100 STR R2, [R3,#4]
.text:00000102 POP {R4-R6}
.text:00000104 BX LR
.text:00000106
Налицо очень большая разница в длине кода: 130 байтов у транслятора KEIL и 262 байта у GCC. Если заняться анализом кода, то можно выделить четыре глупости, совершаемые GCC:
1) Он плохо помнит содержимое регистров процессора. Например, загрузив в самом начале базовый адрес RCC в регистр 3 (смещение 2), он почти сразу пересылает его в регистр 1 (смещение C), вместо того, чтобы продолжать пользоваться регистром 3. Аналогичным образом он поступает и дальше. Как следствие, ему не хватило рабочих регистров (R0-R3, R12), и он сохранил в стеке ещё три регистра (R4-R6).
2) Он вставляет фактически бессмысленные команды внутрь циклов (смещения 22, 6C, 86 -- MOVы, которые надо было бы вынести из циклов).
3) Иногда он зачем-то вновь загружает эти значения и сразу же опять записывает (пара LDR-STR по смещению FE-100). Помимо раздувания кода, это может привести вообще к некорректной работе программы: это же не обычные ячейки памяти, а регистры устройств. При каких условиях генерируется подобный код, пока неясно.
4) Наконец, он банально глупо грузит константы, собирая их по кусочкам с помощью MOVов (со смещения A6) вместо прямой загрузки из памяти с помощью LDR, как это делает KEIL (со смещения 54).
В итоге получаем жутко раздутый код (в 2 раза длиннее, чем надо), да к тому же потенциально некорректно выполняющийся из-за п. 3 (в данном конкретном случае к неприятным последствиям это не приведёт, но...).
В общем, похоже, не зря KEIL стоит больших денег (несколько тыщ евро): код корректный, заметно более качественный, плюс в комплекте IDE (пускай и не верх совершенства, но у GCC вообще ничего нет, надо использовать сторонние вещи), всякие там заголовочные файлы под различные контроллеры...