Производит ли arm-none-eabi-g cc более медленный код, чем Keil uVision - PullRequest
1 голос
/ 25 января 2020

У меня есть простая мигающая светодиодная программа, запущенная на STM32f103C8 (без шаблона инициализации):

void soft_delay(void) {
    for (volatile uint32_t i=0; i<2000000; ++i) { }
}

  uint32_t iters = 0;
  while (1)
  {
    LL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
    soft_delay();
    ++iters;
  }

Она была скомпилирована как Keil uVision v.5 (компилятор по умолчанию), так и CLion с использованием компилятора arm-none-eabi-gcc. Удивляет то, что программа arm-none-eabi-g cc работает на 50% медленнее в режиме Release (-O2 -flto) и на 100% медленнее в режиме Debug.

Я подозреваю 3 причины:

  • Чрезмерная оптимизация Keil (маловероятно, потому что код очень прост)

  • недостаточная оптимизация arm-none-eabi-g cc из-за неправильные флаги компилятора (я использую встроенные плагины CLion CMakeLists.txt)

  • Ошибка инициализации, из-за которой у чипа была более низкая тактовая частота с arm-none-eabi-g cc ( предстоит исследовать)

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

ОБНОВЛЕНИЕ 1

Играя с различными уровнями оптимизации Keil Arm CC, я вижу, как это влияет на сгенерированный код. И это сильно сказывается, особенно на времени исполнения. Вот тесты и разбор функции soft_delay() для каждого уровня оптимизации (объем оперативной памяти и Fla sh включает код инициализации).

-O0: объем оперативной памяти: 1032, Fla sh: 1444, время выполнения (20 итераций): 18,7 сек c

soft_delay PROC
        PUSH     {r3,lr}
        MOVS     r0,#0
        STR      r0,[sp,#0]
        B        |L6.14|
|L6.8|
        LDR      r0,[sp,#0]
        ADDS     r0,r0,#1
        STR      r0,[sp,#0]
|L6.14|
        LDR      r1,|L6.24|
        LDR      r0,[sp,#0]
        CMP      r0,r1
        BCC      |L6.8|
        POP      {r3,pc}
        ENDP

-O1: ОЗУ: 1032, Fla sh: 1216, время выполнения (20 итераций): 13,3 сек c

soft_delay PROC
        PUSH     {r3,lr}
        MOVS     r0,#0
        STR      r0,[sp,#0]
        LDR      r0,|L6.24|
        B        |L6.16|
|L6.10|
        LDR      r1,[sp,#0]
        ADDS     r1,r1,#1
        STR      r1,[sp,#0]
|L6.16|
        LDR      r1,[sp,#0]
        CMP      r1,r0
        BCC      |L6.10|
        POP      {r3,pc}
        ENDP

-O2 -Время: RAM: 1032, Fla sh: 1136, Время выполнения (20 итераций): 9,8 сек c

soft_delay PROC
        SUB      sp,sp,#4
        MOVS     r0,#0
        STR      r0,[sp,#0]
        LDR      r0,|L4.24|
|L4.8|
        LDR      r1,[sp,#0]
        ADDS     r1,r1,#1
        STR      r1,[sp,#0]
        CMP      r1,r0
        BCC      |L4.8|
        ADD      sp,sp,#4
        BX       lr
        ENDP

-O3: RAM: 1032, Fla sh: 1176, время выполнения (20 итераций): 9,9 сек c

soft_delay PROC
        PUSH     {r3,lr}
        MOVS     r0,#0
        STR      r0,[sp,#0]
        LDR      r0,|L5.20|
|L5.8|
        LDR      r1,[sp,#0]
        ADDS     r1,r1,#1
        STR      r1,[sp,#0]
        CMP      r1,r0
        BCC      |L5.8|
        POP      {r3,pc}
        ENDP

TODO: бенчмаркинг и разборка для arm-none-eabi-gcc.

Ответы [ 4 ]

3 голосов
/ 26 января 2020

Не уверен, сколько из вас действительно превысило размер ответа при переполнении стека. Я сделал это снова здесь, так что этот второй ответ является демонстрацией того, что может повлиять на результаты производительности, которые может видеть OP, и примерами возможного тестирования этих

STM32F103C8 синей таблетки.

полный исходный код:

fla sh .ld

MEMORY
{
    rom : ORIGIN = 0x08000000, LENGTH = 0x1000
    ram : ORIGIN = 0x20000000, LENGTH = 0x1000
}
SECTIONS
{
    .text : { *(.text*) } > rom
    .rodata : { *(.rodata*) } > rom
    .bss : { *(.bss*) } > ram
}

fla sh .s

.cpu cortex-m0
.thumb

.thumb_func
.global _start
_start:
stacktop: .word 0x20001000
.word reset
.word hang
.word hang

.thumb_func
reset:
    bl notmain
    b hang
.thumb_func
hang:   b .

.align

.thumb_func
.globl PUT32
PUT32:
    str r1,[r0]
    bx lr

.thumb_func
.globl GET32
GET32:
    ldr r0,[r0]
    bx lr

.thumb_func
.globl dummy
dummy:
    bx lr

test.s

.cpu cortex-m0
.thumb

.word 0,0,0
.word 0,0,0,0

.thumb_func
.globl TEST
TEST:
    bx lr

notmain. c

//PA9  TX
//PA10 RX

void PUT32 ( unsigned int, unsigned int );
unsigned int GET32 ( unsigned int );
void dummy ( unsigned int );

#define USART1_BASE 0x40013800
#define USART1_SR   (USART1_BASE+0x00)
#define USART1_DR   (USART1_BASE+0x04)
#define USART1_BRR  (USART1_BASE+0x08)
#define USART1_CR1  (USART1_BASE+0x0C)
#define USART1_CR2  (USART1_BASE+0x10)
#define USART1_CR3  (USART1_BASE+0x14)
//#define USART1_GTPR (USART1_BASE+0x18)
#define GPIOA_BASE  0x40010800
#define GPIOA_CRH   (GPIOA_BASE+0x04)
#define RCC_BASE    0x40021000
#define RCC_APB2ENR (RCC_BASE+0x18)

#define STK_CSR     0xE000E010
#define STK_RVR     0xE000E014
#define STK_CVR     0xE000E018
#define STK_MASK    0x00FFFFFF

static void uart_init ( void )
{
    //assuming 8MHz clock, 115200 8N1
    unsigned int ra;

    ra=GET32(RCC_APB2ENR);
    ra|=1<<2;   //GPIOA
    ra|=1<<14;  //USART1
    PUT32(RCC_APB2ENR,ra);

    //pa9 TX  alternate function output push-pull
    //pa10 RX configure as input floating
    ra=GET32(GPIOA_CRH);
    ra&=~(0xFF0);
    ra|=0x490;
    PUT32(GPIOA_CRH,ra);

    PUT32(USART1_CR1,0x2000);
    PUT32(USART1_CR2,0x0000);
    PUT32(USART1_CR3,0x0000);
    //8000000/16 = 500000
    //500000/115200 = 4.34
    //4 and 5/16 = 4.3125
    //4.3125 * 16 * 115200 = 7948800
    PUT32(USART1_BRR,0x0045);
    PUT32(USART1_CR1,0x200C);
}
static void uart_putc ( unsigned int c )
{
    while(1)
    {
        if(GET32(USART1_SR)&0x80) break;
    }
    PUT32(USART1_DR,c);
}
static void hexstrings ( unsigned int d )
{
    //unsigned int ra;
    unsigned int rb;
    unsigned int rc;

    rb=32;
    while(1)
    {
        rb-=4;
        rc=(d>>rb)&0xF;
        if(rc>9) rc+=0x37; else rc+=0x30;
        uart_putc(rc);
        if(rb==0) break;
    }
    uart_putc(0x20);
}
static void hexstring ( unsigned int d )
{
    hexstrings(d);
    uart_putc(0x0D);
    uart_putc(0x0A);
}

void soft_delay(void) {
    for (volatile unsigned int i=0; i<2000000; ++i) { }
}

int notmain ( void )
{

    PUT32(STK_CSR,4);
    PUT32(STK_RVR,0x00FFFFFF);
    PUT32(STK_CVR,0x00000000);
    PUT32(STK_CSR,5);

    uart_init();
    hexstring(0x12345678);
    hexstring(GET32(0xE000E018));
    hexstring(GET32(0xE000E018));
    return(0);
}

build

arm-none-eabi-as --warn --fatal-warnings -mcpu=cortex-m3 flash.s -o flash.o
arm-none-eabi-as --warn --fatal-warnings -mcpu=cortex-m3 test.s -o test.o
arm-none-eabi-gcc -Wall -Werror -O2 -nostdlib -nostartfiles -ffreestanding  -mthumb -mcpu=cortex-m0 -march=armv6-m -c notmain.c -o notmain.thumb.o
arm-none-eabi-ld -o notmain.thumb.elf -T flash.ld flash.o test.o notmain.thumb.o
arm-none-eabi-objdump -D notmain.thumb.elf > notmain.thumb.list
arm-none-eabi-objcopy notmain.thumb.elf notmain.thumb.bin -O binary
arm-none-eabi-gcc -Wall -Werror -O2 -nostdlib -nostartfiles -ffreestanding  -mthumb -mcpu=cortex-m3 -march=armv7-m -c notmain.c -o notmain.thumb2.o
arm-none-eabi-ld -o notmain.thumb2.elf -T flash.ld flash.o test.o notmain.thumb2.o
arm-none-eabi-objdump -D notmain.thumb2.elf > notmain.thumb2.list
arm-none-eabi-objcopy notmain.thumb2.elf notmain.thumb2.bin -O binary

Вывод UART, как показано

12345678 
00FFE445 
00FFC698

Если я возьму ваш код, сделайте его короче, не весь день.

void soft_delay(void) {
    for (volatile unsigned int i=0; i<0x2000; ++i) { }
}

arm-none-eabi-gcc -c -O0 -mthumb -mcpu=cortex-m0 hello.c -o hello.o

да я знаю, что это м3

arm-none-eabi-gcc --version
arm-none-eabi-gcc (GCC) 5.4.0

дает

00000000 <soft_delay>:
   0:   b580        push    {r7, lr}
   2:   b082        sub sp, #8
   4:   af00        add r7, sp, #0
   6:   2300        movs    r3, #0
   8:   607b        str r3, [r7, #4]
   a:   e002        b.n 12 <soft_delay+0x12>
   c:   687b        ldr r3, [r7, #4]
   e:   3301        adds    r3, #1
  10:   607b        str r3, [r7, #4]
  12:   687b        ldr r3, [r7, #4]
  14:   4a03        ldr r2, [pc, #12]   ; (24 <soft_delay+0x24>)
  16:   4293        cmp r3, r2
  18:   d9f8        bls.n   c <soft_delay+0xc>
  1a:   46c0        nop         ; (mov r8, r8)
  1c:   46bd        mov sp, r7
  1e:   b002        add sp, #8
  20:   bd80        pop {r7, pc}
  22:   46c0        nop         ; (mov r8, r8)
  24:   00001fff    

сначала проверить инфраструктуру тестирования

.cpu cortex-m0
.thumb

.align 8
.word 0,0


.thumb_func
.globl TEST
TEST:
    push {r4,r5,r6,lr}
    mov r4,r0
    mov r5,r1
    ldr r6,[r4]
inner:
    bl soft_delay
    sub r5,#1
    bne inner
    ldr r3,[r4]
    sub r0,r6,r3
    pop {r4,r5,r6,pc}

.align 8

soft_delay:
    bx lr

в окне openocd te lnet

reset halt
flash write_image erase notmain.thumb.elf
reset

дает

12345678 
00001B59 

7001 тактов, если предположить, что джойстик совпадает с процессором, то есть 7001 тактовый генератор, 4 инструкции за л oop.

Шаг назад примечание Я выровнял некоторые вещи

08000108 <TEST>:
 8000108:   b570        push    {r4, r5, r6, lr}
 800010a:   1c04        adds    r4, r0, #0
 800010c:   1c0d        adds    r5, r1, #0
 800010e:   6826        ldr r6, [r4, #0]

08000110 <inner>:
 8000110:   f000 f876   bl  8000200 <soft_delay>
 8000114:   3d01        subs    r5, #1
 8000116:   d1fb        bne.n   8000110 <inner>
 8000118:   6823        ldr r3, [r4, #0]
 800011a:   1af0        subs    r0, r6, r3
 800011c:   bd70        pop {r4, r5, r6, pc}

08000200 <soft_delay>:
 8000200:   4770        bx  lr

оба цикла хорошо выровнены.

Теперь, если я сделаю это:

0800010a <TEST>:
 800010a:   b570        push    {r4, r5, r6, lr}
 800010c:   1c04        adds    r4, r0, #0
 800010e:   1c0d        adds    r5, r1, #0
 8000110:   6826        ldr r6, [r4, #0]

08000112 <inner>:
 8000112:   f000 f875   bl  8000200 <soft_delay>
 8000116:   3d01        subs    r5, #1
 8000118:   d1fb        bne.n   8000112 <inner>
 800011a:   6823        ldr r3, [r4, #0]
 800011c:   1af0        subs    r0, r6, r3
 800011e:   bd70        pop {r4, r5, r6, pc}

просто меняется выравнивание кода, который должен тестировать тестируемый код, теперь я получаю:

00001F40

8000 тиков, чтобы сделать это l oop 1000 раз при этом вызове с тестируемой функцией кода, которая все еще работает выровненный

08000200 <soft_delay>:
 8000200:   4770        bx  lr

.align 8, как правило, не используйте .align с числом на GNU, его поведение не распространяется на цели. .balign лучше. В любом случае, я использовал его, потому что выровняли выровненный TEST, но внутреннее - это то, что я хотел выровнять, поэтому я добавил два слова, чтобы выровнять его.

.align 8
.word 0,0

nop

.thumb_func
.globl TEST
TEST:
    push {r4,r5,r6,lr}
    mov r4,r0
    mov r5,r1
    ldr r6,[r4]
inner:
    bl soft_delay
    sub r5,#1
    bne inner
    ldr r3,[r4]
    sub r0,r6,r3
    pop {r4,r5,r6,pc}

Небольшой обзор кода, чтобы убедиться, что я не сделал здесь можно ошибиться.

r0 - регистр текущего значения синдиката; r1 - число циклов, которые я хочу запустить тестируемый код

Соглашение о вызовах допускает замыкание r0-r3 так, Мне нужно переместить r0 и r1 в энергонезависимые регистры (в соответствии с соглашением о вызовах).

Я хочу указать время выполнения команды до l oop и инструкции после.

поэтому мне нужны два регистра для r0 и r1 и регистр для хранения времени начала, так что r4, r5, r6 и это хорошо вписывается, чтобы в стек помещалось четное количество регистров. Необходимо сохранить lr, чтобы мы могли вернуться.

Теперь мы можем безопасно вызывать soft_delay в l oop, вычитать число, ветвь, если не равно внутреннему, после того, как счетчик закончен, считайте таймер в r3. из выходных данных выше это обратный счетчик, так что вычесть конец из начала, технически так как это 24-битный счетчик, я должен и с 0x00FFFFFF правильно сделать это вычитание, но так как это не будет переворачиваться, я могу предположить эту операцию. результат / возвращаемое значение заносится в r0, выдает все, что включает выдвижение p c для возврата к вызывающей функции C, которая выводит значение r0.

Я думаю, что тестовый код хорош.

чтение регистра CPUID

411FC231 

, что означает r1p1, в то время как TRM, который я использую, написан для r2p1, вы должны быть очень осторожны, чтобы использовать правильный документ, но также иногда использовать текущий документ или все промежуточные, если таковые имеются, чтобы увидеть, что изменилось.

Интерфейс памяти ICode

Выборки инструкций из пространства памяти кода от 0x00000000 до 0x1FFFFFFF выполняются по 32-битной шине AHB-Lite. Отладчик не может получить доступ к этому интерфейсу. Все выборки по всему миру. Количество команд, извлекаемых для каждого слова, зависит от выполняемого кода и выравнивания кода в памяти.

Иногда в ARM TRM вы видите информацию выборки вверху рядом с функциями процессора, это говорит мне, что Я хотел знать.

08000112 <inner>:
 8000112:   f000 f875   bl  8000200 <soft_delay>
 8000116:   3d01        subs    r5, #1
 8000118:   d1fb        bne.n   8000112 <inner>

это требует выборки в 110, 114 и 118.

08000110 <inner>:
 8000110:   f000 f876   bl  8000200 <soft_delay>
 8000114:   3d01        subs    r5, #1
 8000116:   d1fb        bne.n   8000110 <inner>

это выборка в 110 и 114, но не одна в 118, так что дополнительная выборка может быть нашими добавленными часами. m3 был первым общедоступным, и в нем было много функций, которые исчезли, и появились похожие. некоторые из меньших ядер получают по-разному, и вы не видите эту проблему выравнивания. с более крупными ядрами, такими как полноразмерные, они иногда получают 4 или 8 инструкций за раз, и вам нужно еще больше изменить свое выравнивание, чтобы попасть на границу, но вы можете попасть на границу, и так как это 2 или 4 такта плюс служебная нагрузка шины для дополнительного получить, вы можете увидеть их.

Если я поставлю два nops

nop
nop

.thumb_func
.globl TEST
TEST:

дает

08000114 <inner>:
 8000114:   f000 f874   bl  8000200 <soft_delay>
 8000118:   3d01        subs    r5, #1
 800011a:   d1fb        bne.n   8000114 <inner>
 800011c:   6823        ldr r3, [r4, #0]
 800011e:   1af0        subs    r0, r6, r3
 8000120:   bd70        pop {r4, r5, r6, pc}

дает

00001B59 

так что это хорошо, мы вернулись к этому числу, могли бы попробовать еще несколько для подтверждения, но похоже, что выравнивание чувствительно к нашему внешнему тесту l oop, что плохо, но мы можем справиться с этим, не меняйте его, это не повлияет на тест. Если я не заботился о выравнивании и имел что-то вроде этого:

void soft_delay(void) {
    for (volatile unsigned int i=0; i<0x2000; ++i) { }
}
int notmain ( void )
{
    unsigned int ra;
    unsigned int beg;
    unsigned int end;

    PUT32(STK_CSR,4);
    PUT32(STK_RVR,0x00FFFFFF);
    PUT32(STK_CVR,0x00000000);
    PUT32(STK_CSR,5);

    uart_init();
    hexstring(0x12345678);
    beg=GET32(STK_CVR);
    for(ra=0;ra<1000;ra++)
    {
        soft_delay();
    }
    end=GET32(STK_CVR);
    hexstring((beg-end)&0x00FFFFFF);
    return(0);
}

тогда, когда я играл с опциями оптимизации, и я также играл с использованием разных компиляторов, любые изменения в программе / двоичном коде перед тестом l oop переместит / может переместить тест oop, изменив его производительность, в моем простом примере это была разница в производительности на 14%, что огромно, если вы проводите тесты производительности. позволить компилятору позаботиться обо всем этом без того, чтобы мы не контролировали все, что находится перед тестируемой функцией, и могли запутаться в тестируемой функции, как написано выше, компилятор может предпочесть встроить функцию, а не вызывать ее, что делает еще более интересным Ситуация как тест l oop, хотя, вероятно, не так чиста, как моя, конечно, не если не оптимизирована, но теперь тестируемый код является динамическим c при изменении параметров или выравниваний.

Я очень рад, что вам довелось использовать это ядро ​​/ чип ...

Если я перенастрою внутреннюю часть и теперь связываюсь с этим

.align 8
nop
soft_delay:
    bx lr

08000202 <soft_delay>:
 8000202:   4770        bx  lr

, это один инструкция, которая взята в 0x200 из того, что мы прочитали и, кажется, можем сказать. не ожидал, что это что-то изменит, и это не

00001B59

, но теперь, когда мы знаем то, что мы знаем, мы можем использовать наш опыт, чтобы связываться с этим тривиальным примером. Не интересно вообще.

.align 8
nop
soft_delay:
    nop
    bx lr

дает

00001F41

, как и ожидалось. и мы можем получить еще больше удовольствия:

.align 8
.word 0,0
nop
.thumb_func
.globl TEST
TEST:

вместе дает

08000112 <inner>:
 8000112:   f000 f876   bl  8000202 <soft_delay>
 8000116:   3d01        subs    r5, #1
 8000118:   d1fb        bne.n   8000112 <inner>

08000202 <soft_delay>:
 8000202:   46c0        nop         ; (mov r8, r8)
 8000204:   4770        bx  lr

не удивительно, если вы знаете, что делаете:

00002328 

9000 часов, Разница в производительности 29%. мы в буквальном смысле говорим о 5 (технически 6) инструкциях, одинаковом точном машинном коде, и при простом изменении выравнивания производительность может быть разной на 29%, компилятор и опции не имеют к этому никакого отношения, но даже не попали туда.

Как мы можем ожидать какой-либо оценки производительности программы, используя код несколько раз в методе oop? Мы не можем, если не знаем, что делаем, не понимаем архитектуру и т. Д. c.

Теперь, как это должно быть очевидно, и, читая документацию, я использую внутренние часы 8 МГц, все происходит из поэтому время нахождения курсора не будет меняться, как, например, в случае с драмом. Биты LATENCY в регистре FLASH_ACR должны были по умолчанию установить нулевое состояние ожидания для 0

Без путаницы с часами и простого добавления состояния ожидания путем изменения FLASH_ACR регистрируется в 0x31.

000032C6 

12998 по сравнению с 9000, я не ожидал, что он обязательно удвоится, и это не так.

Хм, для удовольствия сделайте PUT16, используя strh, и

.thumb_func
.globl HOP
HOP:
    bx r2

и

PUT16(0x2000010a,0xb570); // 800010a:  b570        push    {r4, r5, r6, lr}
PUT16(0x2000010c,0x1c04); // 800010c:  1c04        adds    r4, r0, #0
PUT16(0x2000010e,0x1c0d); // 800010e:  1c0d        adds    r5, r1, #0
PUT16(0x20000110,0x6826); // 8000110:  6826        ldr r6, [r4, #0]

PUT16(0x20000112,0xf000); // 8000112:  f000 f876   bl  8000202 <soft_delay>
PUT16(0x20000114,0xf876); // 8000112:  f000 f876   bl  8000202 <soft_delay>

PUT16(0x20000116,0x3d01); // 8000116:  3d01        subs    r5, #1
PUT16(0x20000118,0xd1fb); // 8000118:  d1fb        bne.n   8000112 <inner>
PUT16(0x2000011a,0x6823); // 800011a:  6823        ldr r3, [r4, #0]
PUT16(0x2000011c,0x1af0); // 800011c:  1af0        subs    r0, r6, r3
PUT16(0x2000011e,0xbd70); // 800011e:  bd70        pop {r4, r5, r6, pc}

PUT16(0x20000202,0x46c0); // 8000202:  46c0        nop         ; (mov r8, r8)
PUT16(0x20000204,0x4770); // 8000204:  4770        bx  lr

    hexstring(HOP(STK_CVR,1000,0x2000010B));

дают 0000464B

, и это совсем не ожидалось. но в основном это 18000

После этого укладывает баранов в постель

PUT16(0x20000108,0xb570); // 800010a:  b570        push    {r4, r5, r6, lr}
PUT16(0x2000010a,0x1c04); // 800010c:  1c04        adds    r4, r0, #0
PUT16(0x2000010c,0x1c0d); // 800010e:  1c0d        adds    r5, r1, #0
PUT16(0x2000010e,0x6826); // 8000110:  6826        ldr r6, [r4, #0]

PUT16(0x20000110,0xf000); // 8000112:  f000 f876   bl  8000202 <soft_delay>
PUT16(0x20000112,0xf876); // 8000112:  f000 f876   bl  8000202 <soft_delay>

PUT16(0x20000114,0x3d01); // 8000116:  3d01        subs    r5, #1
PUT16(0x20000116,0xd1fb); // 8000118:  d1fb        bne.n   8000112 <inner>
PUT16(0x20000118,0x6823); // 800011a:  6823        ldr r3, [r4, #0]
PUT16(0x2000011a,0x1af0); // 800011c:  1af0        subs    r0, r6, r3
PUT16(0x2000011c,0xbd70); // 800011e:  bd70        pop {r4, r5, r6, pc}

PUT16(0x20000200,0x46c0); // 8000202:  46c0        nop         ; (mov r8, r8)
PUT16(0x20000200,0x4770); // 8000204:  4770        bx  lr
hexstring(HOP(STK_CVR,1000,0x20000109));

00002EDE

Машинный код не изменился, потому что я переместил оба назад на 2, поэтому относительный адрес между ними был одинаковым. Обратите внимание, что bl - это две отдельные инструкции, а не одна 32-битная. Вы не можете увидеть это в новых документах, которые вам нужны, чтобы go вернуться к исходному / раннему ARM ARM, где это объясняется. И легко проводить эксперименты, в которых вы разделяете две инструкции и помещаете другие элементы между ними, и они работают просто отлично, потому что это две отдельные инструкции.

На этом этапе читатель должен быть в состоянии выполнить тест из 2 команд l oop, рассчитать его и кардинально изменить производительность выполнения этих двух инструкций на этой платформе, используя один и тот же точный машинный код.

Итак, давайте попробуем изменчивый l oop что вы написали.

.align 8

soft_delay:
    push {r7, lr}
    sub sp, #8
    add r7, sp, #0
    mov r3, #0
    str r3, [r7, #4]
    b L12
 Lc:
    ldr r3, [r7, #4]
    add r3, #1
    str r3, [r7, #4]
 L12:
    ldr r3, [r7, #4]
    ldr r2, L24
    cmp r3, r2
    bls Lc
    nop
    mov sp, r7
    add sp, #8
    pop {r7, pc}
    nop

 .align
 L24:   .word 0x1FFF

это, я считаю, неоптимизированная версия -O0. начиная с одного теста l oop

hexstring(TEST(STK_CVR,1));

опыт, время, которое мы видим, переполнит наш 24-битный счетчик, и результаты будут очень странными или приведут к ложным выводам.

0001801F

98 000, быстрая проверка на безопасность:

.align
 L24:   .word 0x1F

0000019F

неплохо, что в 256 раз быстрее.

так что у нас есть немного места для маневра в наш тест l oop, но не слишком много, попытка 10

hexstring(TEST(STK_CVR,10));

000F012D

98334 тиков на л oop.

изменение выравнивания

08000202 <soft_delay>:
 8000202:   b580        push    {r7, lr}
 8000204:   b082        sub sp, #8

дала тот же результат

000F012D

не неслыханно, вы можете проверить различия, если хотите подсчитать через каждый цикл выборки проверки команд и т. Д. c.

если бы я сделал тест:

soft_delay:
  nop
  nop
  bx lr

это два цикла выборки, независимо от того, какое выравнивание или если я оставил его bx lr без nops, как мы видели, просто имея нечетное количество инструкций в тесте, тогда выравнивание не повлияет на результаты при выборках вдоль, но обратите внимание, что из того, что мы знаем сейчас, был какой-то другой код в р rogram перенес внешнюю синхронизацию / тест l oop, который, возможно, изменил производительность, и результаты могут показать разницу между двумя тестами, которые были чисто временным кодом, а не тестируемым кодом (читай Майкла Абра sh).

Cortex-m3 основан на архитектуре armv7-m. Если я изменю компилятор с -mcpu = cortex-m0 (пока что все совместимы с cortex-m) на -mcpu = cortex-m3 (не все совместимые с cortex-m сломаются на половине из них), он производит немного меньше кода.

.align 8

soft_delay:
    push {r7}
    sub  sp, #12
    add  r7, sp, #0
    movs r3, #0
    str  r3, [r7, #4]
    b L12
Lc:
    ldr r3, [r7, #4]
    add r3, #1
    str r3, [r7, #4]
L12:
    ldr r3, [r7, #4]
    /*14:   f5b3 5f00   cmp.w   r3, #8192   ; 0x2000*/
    //cmp.w r3, #8192
    .word 0x5f00f5b3
    bcc Lc
    nop
    add r7, #12
    mov sp, r7
    pop {r7}
    bx  lr

000C80FB 81945 тиков для тестируемого кода.

Я ненавижу унифицированный синтаксис, это была огромная ошибка, поэтому я возился в унаследованном режиме. таким образом, вещь слова в середине.

Как часть написания этого, я как бы испортил свою систему, чтобы что-то продемонстрировать. Я собирал ag cc 5.4.0, но переписал мой 9.2.0, поэтому пришлось пересобрать оба.

2.95 была версия, которую я начал использовать с arm и не поддерживал thumb g cc 3.xx был первым. И либо g cc 4.xx, либо g cc 5.xx создавали «более медленный» код для некоторых из моих проектов, на работе мы в настоящее время переходим с ubuntu 16.04 на 18.04 для наших систем сборки, которые, если вы используете apt- у нас есть кросс-компилятор для arm, который перемещает вас с 5.xx на 7.xx, и он делает большие двоичные файлы для того же исходного кода, и там, где у нас не хватает памяти, он выталкивает нас за пределы того, что доступно, поэтому нам нужно либо удалить некоторый код ( проще всего сделать печатные сообщения короче, вырезать текст) или придерживаться старого компилятора, создав собственный или удачно получив старый. 19.10 больше не предлагает версию 5.xx.

Таким образом, теперь оба они построены.

  18:   d3f8        bcc.n   c <soft_delay+0xc>
  1a:   bf00        nop
  1c:   bf00        nop
  1e:   370c        adds    r7, #12

эти nops после b cc сбивают меня с толку ...

  18:   d3f8        bcc.n   c <soft_delay+0xc>
  1a:   bf00        nop
  1c:   370c        adds    r7, #12

g cc 5.4.0 ставит один, g cc 9.2.0 ставит два nops, ARM не имеет функции MIPS для теневого ветвления (MIPS в настоящее время тоже).

000C80FB gcc 5.4.0
000C8105 gcc 9.2.0

Я вызываю функцию 10 раз, nop находится за пределами тестируемого кода l oop, поэтому имеет меньший эффект.

Оптимизирован все варианты cortex-m (на сегодняшний день) с помощью g cc 9.2 .0

soft_delay:
    mov r3, #0
    mov r2, #128
    sub sp, #8
    str r3, [sp, #4]
    ldr r3, [sp, #4]
    lsl r2, r2, #6
    cmp r3, r2
    bcs L1c
L10:
    ldr r3, [sp, #4]
    add r3, #1
    str r3, [sp, #4]
    ldr r3, [sp, #4]
    cmp r3, r2
    bcc L10
L1c:
    add sp, #8
    bx  lr

(также понимаю, что не все говорят g cc 9.2. 0 сборок дают один и тот же код, когда вы собираете компилятор, у вас есть опции, и эти опции могут влиять на вывод, делая разные сборки 9.2.0, возможно, приводящие к разным результатам)

000C80B5

g cc 9.2.0 build для cortex-m3:

soft_delay:
    mov r3, #0
    sub sp, #8
    str r3, [sp, #4]
    ldr r3, [sp, #4]
/*8:  f5b3 5f00   cmp.w   r3, #8192   ; 0x2000*/
    .word 0x5F00F5B3
    bcs L1c
Le:
    ldr r3, [sp, #4]
    add r3, #1
    str r3, [sp, #4]
    ldr r3, [sp, #4]
/*16: f5b3 5f00   cmp.w   r3, #8192   ; 0x2000*/
    .word 0x5F00F5B3
    bcc Le
L1c:
    add sp, #8
    bx  lr

000C80A1

вот в шуме. несмотря на встроенный код имеет различия. они просто не выиграли в сравнении 0x2000 в меньшем количестве инструкций. и обратите внимание, что если вы измените это значение 0x2000 на какое-то другое число, то это просто не заставит l oop намного дольше изменять сгенерированный код для подобных архитектур.

Как мне нравится делать эти отсчитанные задержки Циклы должны использовать функцию вне домена компиляции

extern void dummy ( unsigned int );
void soft_delay(void) {
    for (unsigned int i=0; i<0x2000; ++i) { dummy(i); }
}

soft_delay:
    push {r4, r5, r6, lr}
    mov r5, #128
    mov r4, #0
    lsl r5, r5, #6
L8:
    mov r0, r4
    add r4, #1
    bl dummy
    cmp r4, r5
    bne L8
    pop {r4, r5, r6, pc}

функция, которая есть, вам не нужна дополнительная информация о том, что volatile у вас есть вызов, и очевидно, что есть также дополнительные затраты из-за вызова, но не

000B40C9

или даже лучше:

soft_delay:
    sub r0,#1
    bne soft_delay
    bx lr

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

Другой обратите внимание, указав c к этим целям, но также к чему-то, с чем вы имеете дело

unsigned int more_fun ( unsigned int, unsigned int );
unsigned int fun ( unsigned int a, unsigned int b )
{
    return(more_fun(a,b)+a+(b<<2));
}



00000000 <fun>:
   0:   b570        push    {r4, r5, r6, lr}
   2:   000c        movs    r4, r1
   4:   0005        movs    r5, r0
   6:   f7ff fffe   bl  0 <more_fun>
   a:   00a4        lsls    r4, r4, #2
   c:   1964        adds    r4, r4, r5
   e:   1820        adds    r0, r4, r0
  10:   bd70        pop {r4, r5, r6, pc}
  12:   46c0        nop         ; (mov r8, r8)

вопрос, повторенный здесь в SO на периодической основе. почему он нажимает на r6, он не использует r6.

Компилятор работает с использованием того, что я называю, и раньше назывался соглашением о вызовах, теперь они используют термины ABI, EABI, в любом случае это одно и то же набор правил, которым следует компилятор для конкретной цели. Arm добавил правило для выравнивания стека на границе 64-битного адреса вместо 32, что заставило дополнительный элемент сохранять выравнивание стека, то, какой регистр там используется, может варьироваться. Если вы используете более старую версию g cc против более новой, это может / повлияет на производительность вашего кода.

2 голосов
/ 25 января 2020

здесь много факторов. Конечно, если у вас есть оптимизирующий компилятор и вы сравниваете оптимизированный с не зависящим от кода, вы можете увидеть большую разницу в скорости выполнения. Использование volatile в крошечном l oop здесь фактически маскирует часть этого, в обоих случаях его следует читать / записывать в память каждый l oop.

Но код вызова неоптимизированной переменной l oop будет касаться оперативной памяти два или три раза в этом l oop, в идеале оптимизированный будет все время находиться в регистре, создавая драматическую разницу c в производительности выполнения даже при нулевом значении состояния ожидания.

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

Добавьте, что это mcu и работает из fla sh, что с возрастом этой части fla sh может в лучшем случае быть половиной тактовой частоты процессора и наихудшим числом состояний ожидания, и я не припомню, если бы ST имел кеширование перед ним в то время. поэтому каждая добавляемая вами инструкция может добавлять часы, так что только одна переменная l oop может резко изменить время.

Будучи высокопроизводительным конвейерным ядром, я продемонстрировал здесь и в других местах, что выравнивание может (не всегда) играть роль, поэтому, если в одном случае один и тот же машинный код связывается с адресом 0x100 в одном случае и 0x102 в другом, возможно, что точно такой же машинный код требует дополнительных или меньших тактов для выполнения в зависимости от природы устройства предварительной выборки в дизайн или реализация fla sh, кеш, если есть реализация, предсказатель ветвления и т. д. c.

И тогда самая большая проблема заключается в том, как вы справились с этим, не редкость быть там ошибка в неправильном использовании часов, так что сам код часов / времени меняется и создает некоторую разницу. Кроме того, существуют ли фоновые события, прерывания / многозадачность.

Михал Абра sh написал замечательную книгу под названием «Язык дзен на ассемблере», вы можете получить ее бесплатно в формате epub или, возможно, в формате pdf на github. 8088 был устаревшим, когда книга была выпущена, но если вы сосредоточились на этом, то вы упустили момент, я купил его, когда он вышел, и использовал то, что я узнал почти ежедневно.

g cc это не высокопроизводительный компилятор, это скорее компилятор общего назначения, созданный в стиле unix, где вы можете иметь разные языковые и разные целевые бэкэнды. Когда я был в том положении, в котором вы сейчас пытаетесь вначале понять эти вещи, я выбрал много компиляторов для одной и той же цели цели и того же кода C, и результаты были огромными. Позже я написал имитатор набора команд, чтобы я мог сосчитать инструкции и обращения к памяти для сравнения gnu с llvm, так как последний имеет больше возможностей оптимизации, чем gnu, но для тестов выполнения кода g cc иногда выполнялся быстрее, но не медленнее. Это закончилось тем, что я провел больше веселых выходных, чем то, что я использовал для анализа различий.

Проще начать с кода типа small-i sh, подобного этому, и разобрать оба. Поймите, что меньшее количество инструкций не означает более быстрого, один удаленный доступ к памяти в основанной на драмах системе может занять сотни тактовых циклов, которые могут быть заменены другим решением, которое использует горстку / дюжину линейно извлекаемых инструкций для получения того же результата (сделать некоторые математические и другие ищут что-то в редко используемой таблице), и в зависимости от ситуации дюжина инструкций выполняется намного быстрее. в то же время решение таблицы может быть намного быстрее. это зависит.

проверка разборки часто приводит к неверным выводам (читайте abra sh, не только эту книгу, все), так как сначала люди думают, что меньше инструкций означает более быстрый код. перестановка команд в конвейерном процессоре может улучшить производительность, если вы переместите инструкцию в период времени, который иначе был бы потрачен впустую. увеличение регистра, не связанного с доступом к памяти перед доступом к памяти, а не после, в процессоре без суперскалера.

ааа, вернемся к комментарию. это были годы go, и конкурирующие компиляторы были чем-то большим, что большинство людей просто оборачивали gui вокруг gnu, и ide / gui - это продукт, а не компилятор. Но был сам компилятор arm, до того как инструменты rvct, реклама и я забыли другие, они были «лучше», чем g cc. Я забыл названия других, но был один, который создавал значительно более быстрый код, при условии, что это был dhrystone, так что вы также обнаружите, что они могут настраивать оптимизаторы для dhrystone просто для того, чтобы играть в тестовые игры. Теперь, когда я вижу, как легко манипулировать тестами, я считаю, что они в целом не могут быть доверенными. Раньше Киль был многоцелевым инструментом для mcus и аналогичных, но затем был куплен рукой, и я думал, что в то время, когда они сбрасывали все остальные цели, но не проверял в течение некоторого времени. Возможно, я однажды попробовал их, чтобы получить доступ к бесплатной / демо-версии rvct, поскольку, когда я работал на одной работе, у нас был бюджет на покупку инструментов стоимостью в несколько тысяч долларов, но это не включало rvct (хотя я разговаривал по телефону с бывшие любезные люди, которые были частью покупки, которая стала инструментом rvct), которую я очень хотел попробовать, как только они закончили разработку / интеграцию этого продукта, к тому времени не имели бюджета для этого и позже не могли позволить себе или не интересовались покупка даже кильских инструментов, намного меньше оружия. Их ранние демоверсии rvct создали зашифрованный / обфусцированный двоичный файл, который не был машинным кодом, который запускался только на их симуляторе, так что вы не могли использовать его для оценки производительности или сравнения его с другими, не думайте, что они захотели дать нам необъяснимый версия, и мы не были готовы пересмотреть его. теперь проще просто использовать g cc или clang и вручную оптимизировать, где НУЖНО. Точно так же с опытом можно написать C код, который лучше оптимизирует, основываясь на опыте проверки выходных данных компилятора.

Вы должны знать аппаратное обеспечение, особенно в этом случае, когда вы берете IP процессора, а большая часть чипа не связана с IP-адрес процессора и большая часть производительности не связаны с IP-адресом процессора (в значительной степени справедливо для многих современных платформ, в частности для вашего сервера / настольного компьютера / ноутбука). Например, в геймбойском прогрессе вместо 32 использовалось много 16-битных шин, инструменты для большого пальца практически не интегрировались, но режим большого пальца при подсчете инструкций или байтов был на 10% больше кода в то время, который выполнялся на этом чипе значительно быстрее. В других реализациях как архитектура плеча, так и большой дизайн чипа могут сказываться на производительности или нет.

ST в целом с продуктами cortex-m склонны помещать кэш перед fla sh, иногда они документируют его и предоставляют управление включением / отключением, иногда нет, поэтому в лучшем случае это может быть трудно получить реальным значением производительности, как правило, является многократный запуск тестируемого кода в al oop, чтобы вы могли получить лучшее измерение времени. другие поставщики не обязательно делают это, и гораздо легче увидеть состояния ожидания fla sh и получить реальное, наихудшее, временное значение, которое вы можете использовать для проверки вашего проекта. Кэширование в целом, а также конвейеры в лучшем случае затрудняют получение хороших, воспроизводимых, надежных чисел для проверки вашего дизайна. Так, например, вы иногда не можете выполнить трюк выравнивания, чтобы связываться с производительностью того же машинного кода на компьютере, но, скажем, на том же ядре, что и вы. st может не дать вам icache в cortex-m7, как мог бы сделать другой поставщик, так как st уже рассмотрел это. Даже в пределах одной торговой марки не ожидайте, что результаты одного чипа / семейства будут преобразованы в другой чип / семейство, даже если они используют то же ядро. Также обратите внимание на тонкие комментарии в документации для руководства о том, предлагают ли некоторые ядра размер выборки в качестве объявленной опции, с одним или несколькими циклами, и т. Д. c. и плохо скажу вам, что существуют другие опции времени компиляции для ядра, которые не показаны в техническом справочном руководстве, которые могут повлиять на производительность, поэтому не думайте, что все cortex-m3 одинаковы, даже если они имеют одинаковую ревизию от руки. у производителя микросхемы есть источник, так что он может go еще больше и модифицировать его, или, например, банк регистров, который будет реализован потребителем, может изменить его с отсутствия защиты на паритет на e cc, что может повлиять на производительность при сохранении все оружие оригинального кода как есть. Когда вы смотрите на avr или pi c, хотя или даже на msp430, хотя я не могу доказать, что эти проекты выглядят более статично c не крошечные по сравнению с xmega по сравнению с обычными старыми avr, поскольку в них есть определенные различия, но одно крошечное к другому .

Ваши предположения - хорошее начало, действительно нет такой вещи, как чрезмерная оптимизация, скорее пропущенная оптимизация, но могут быть и другие факторы, которые вы не видите в своих предположениях, которые могут или не могут быть быть в разборке. есть очевидные вещи, которые можно ожидать, например, одна из переменных l oop будет основана на регистре, а не на памяти. При выравнивании я не ожидал бы, что настройки часов изменятся, если вы использовали один и тот же код, но другой набор экспериментов с использованием таймеров или области, в которой вы можете измерить настройки часов, чтобы увидеть, были ли они настроены одинаково. фоновые задания, прерывания и тупая удача относительно того, как / когда они попали в тест. Но суть в том, что иногда это так же просто, как пропущенная оптимизация или тонкие различия в том, как один компилятор генерирует код для другого, часто это не те вещи, а скорее проблема системы, скорости памяти, скорости периферии, кэшей или их архитектуры, как работают различные шины в конструкции, et c. Для некоторых из этих cortex-ms (и многих других процессоров) вы можете использовать их поведение шины, чтобы показать разницу в производительности в том, чего не ожидал бы обычный человек.

1 голос
/ 26 января 2020

Переоптимизация Keil (маловероятно, потому что код очень прост)

Вы не можете переоптимизировать, вы можете потерять / упустить, так что если что-то g cc пропустило что-то, что убило техника его подводит. Не наоборот

недоптимизация arm-none-eabi-g cc из-за неправильных флагов компилятора (я использую CMakeLists.txt плагинов для встроенных плагинов CLion)

будет показано ниже, но это, скорее всего, esp debug vs release, я никогда не собираюсь для отладки (никогда не использую отладчик), вам придется тестировать все дважды, а если вы не тестируете, как go, то это затрудняет отладку поэтому в версии выпуска, если есть проблемы, требуется гораздо больше усилий, чтобы выяснить проблемы.

Ошибка инициализации, из-за которой у чипа более низкая тактовая частота с arm-none-eabi-g cc (подлежит расследованию)

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

давайте его запустим.

Использование 24-битного таймера систита (адрес регистра текущего значения передан в r0)

.align 8

.thumb_func
.globl TEST
TEST:
    push {r4,r5,r6,lr}
    mov r4,r0
    ldr r5,[r4]

    bl soft_delay

    ldr r3,[r4]
    sub r0,r5,r3
    pop {r4,r5,r6,pc}

, чтобы избежать переполнения 24-битного таймера, количество циклов ограничено до 200000 раз нет 2000000 раз. Я предполагаю, что код, который вы пропустили, равен 2000000 - 1. Если нет, то он по-прежнему показывает соответствующие различия.

-O0 код

.align 8

soft_delay:
    PUSH     {r3,lr}
    MOV      r0,#0
    STR      r0,[sp,#0]
    B        L6.14
L6.8:
    LDR      r0,[sp,#0]
    ADD      r0,r0,#1
    STR      r0,[sp,#0]
L6.14:
    LDR      r1,L6.24
    LDR      r0,[sp,#0]
    CMP      r0,r1
    BCC      L6.8
    POP      {r3,pc}

.align

L6.24: .word 100000 - 1

08000200 <soft_delay>:
 8000200:   b508        push    {r3, lr}
 8000202:   2000        movs    r0, #0
 8000204:   9000        str r0, [sp, #0]
 8000206:   e002        b.n 800020e <L6.14>

08000208 <L6.8>:
 8000208:   9800        ldr r0, [sp, #0]
 800020a:   3001        adds    r0, #1
 800020c:   9000        str r0, [sp, #0]

0800020e <L6.14>:
 800020e:   4902        ldr r1, [pc, #8]    ; (8000218 <L6.24>)
 8000210:   9800        ldr r0, [sp, #0]
 8000212:   4288        cmp r0, r1
 8000214:   d3f8        bcc.n   8000208 <L6.8>
 8000216:   bd08        pop {r3, pc}

08000218 <L6.24>:
 8000218:   0001869f

00124F8B systick timer ticks

-O1 код

soft_delay:
    PUSH     {r3,lr}
    MOV      r0,#0
    STR      r0,[sp,#0]
    LDR      r0,L6.24
    B        L6.16
L6.10:
    LDR      r1,[sp,#0]
    ADD      r1,r1,#1
    STR      r1,[sp,#0]
L6.16:
    LDR      r1,[sp,#0]
    CMP      r1,r0
    BCC      L6.10
    POP      {r3,pc}
.align
L6.24: .word 100000 - 1


08000200 <soft_delay>:
 8000200:   b508        push    {r3, lr}
 8000202:   2000        movs    r0, #0
 8000204:   9000        str r0, [sp, #0]
 8000206:   4804        ldr r0, [pc, #16]   ; (8000218 <L6.24>)
 8000208:   e002        b.n 8000210 <L6.16>

0800020a <L6.10>:
 800020a:   9900        ldr r1, [sp, #0]
 800020c:   3101        adds    r1, #1
 800020e:   9100        str r1, [sp, #0]

08000210 <L6.16>:
 8000210:   9900        ldr r1, [sp, #0]
 8000212:   4281        cmp r1, r0
 8000214:   d3f9        bcc.n   800020a <L6.10>
 8000216:   bd08        pop {r3, pc}

08000218 <L6.24>:
 8000218:   0001869f

000F424E systicks

-O2 код

soft_delay:
    SUB      sp,sp,#4
    MOVS     r0,#0
    STR      r0,[sp,#0]
    LDR      r0,L4.24
L4.8:
    LDR      r1,[sp,#0]
    ADDS     r1,r1,#1
    STR      r1,[sp,#0]
    CMP      r1,r0
    BCC      L4.8
    ADD      sp,sp,#4
    BX       lr
.align
L4.24: .word 100000 - 1

08000200 <soft_delay>:
 8000200:   b081        sub sp, #4
 8000202:   2000        movs    r0, #0
 8000204:   9000        str r0, [sp, #0]
 8000206:   4804        ldr r0, [pc, #16]   ; (8000218 <L4.24>)

08000208 <L4.8>:
 8000208:   9900        ldr r1, [sp, #0]
 800020a:   3101        adds    r1, #1
 800020c:   9100        str r1, [sp, #0]
 800020e:   4281        cmp r1, r0
 8000210:   d3fa        bcc.n   8000208 <L4.8>
 8000212:   b001        add sp, #4
 8000214:   4770        bx  lr
 8000216:   46c0        nop         ; (mov r8, r8)

08000218 <L4.24>:
 8000218:   0001869f

000AAE65 systicks

-O3

soft_delay:
        PUSH     {r3,lr}
        MOV      r0,#0
        STR      r0,[sp,#0]
        LDR      r0,L5.20
L5.8:
        LDR      r1,[sp,#0]
        ADD      r1,r1,#1
        STR      r1,[sp,#0]
        CMP      r1,r0
        BCC      L5.8
        POP      {r3,pc}
.align
L5.20: .word 100000 - 1


08000200 <soft_delay>:
 8000200:   b508        push    {r3, lr}
 8000202:   2000        movs    r0, #0
 8000204:   9000        str r0, [sp, #0]
 8000206:   4803        ldr r0, [pc, #12]   ; (8000214 <L5.20>)

08000208 <L5.8>:
 8000208:   9900        ldr r1, [sp, #0]
 800020a:   3101        adds    r1, #1
 800020c:   9100        str r1, [sp, #0]
 800020e:   4281        cmp r1, r0
 8000210:   d3fa        bcc.n   8000208 <L5.8>
 8000212:   bd08        pop {r3, pc}

08000214 <L5.20>:
 8000214:   0001869f

000AAE6A systicks

Интересно, что выравнивание не влияет ни на один из этих результатов.

Сравнение ваших результатов относительно друг друга и выше в электронная таблица

18.7    1.000   00124F8B    1200011 1.000
13.3    0.711   000F424E    1000014 0.833
 9.8    0.524   000AAE65     700005 0.583
 9.9    0.529   000AAE6A     700010 0.583

Это показывает, что различные этапы, которые я измерил, также показывают улучшения, и что -O3 немного медленнее.

Анализ того, что произошло.

void soft_delay (void) {for (volatile uint32_t i = 0; i <2000000; ++ i) {}} </p>

, так как это подсчитывает И является нестабильным, компилятор не может выполнить обычный обратный отсчет и сохранить инструкцию (подпрограммы затем bne, а не add, cmp, b cc)

-O0 code

soft_delay:
    PUSH     {r3,lr}      allocate space for i
    MOV      r0,#0        i = 0
    STR      r0,[sp,#0]   i = 0
    B        L6.14
L6.8:
    LDR      r0,[sp,#0]   read i from memory
    ADD      r0,r0,#1     increment i
    STR      r0,[sp,#0]   save i to memory
L6.14:
    LDR      r1,L6.24     read max value 
    LDR      r0,[sp,#0]   read i from memory
    CMP      r0,r1        compare i and max value
    BCC      L6.8         branch if unsigned lower
    POP      {r3,pc}      return

Я должен был изучить код. Сначала L6.24 должен был быть 2000000, а не 2000000 - 1. Вы оставили это вне своего вопроса.

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

r3 не нуждается в сохранении, равно как и LR, но переменная является энергозависимой, поэтому ей требуется пространство в стеке, которое компилятор решил сделать таким образом для этого уровня оптимизации, нажав lr, чтобы он выскочил p c в конце.

pu sh - псевдоинструкция для stm (stmdb), поэтому 8 вычитается из указателя стека, затем регистры сохраняются по порядку, поэтому, если sp был в 0x1008, то он меняется на 0x1000 и записывает r3 в От 0x1000 и lr до 0x1004, поэтому для остальной части этой функции используется sp + 0, который в данном примере равен 0x1000. R3 и pu sh, используемые таким образом, предназначены для выделения местоположения для переменной i в коде.

-O1 версия

soft_delay:
    PUSH     {r3,lr}     allocate space
    MOV      r0,#0       i = 0
    STR      r0,[sp,#0]  i = 0
    LDR      r0,L6.24    read max/test value
    B        L6.16
L6.10:
    LDR      r1,[sp,#0]  load i from memory
    ADD      r1,r1,#1    increment i
    STR      r1,[sp,#0]  save i to memory
L6.16:
    LDR      r1,[sp,#0]  read i from memory
    CMP      r1,r0       compare i with test value
    BCC      L6.10       branch if unsigned lower
    POP      {r3,pc}     

Основная разница между -O0 и -O1 в этом случае версия -O0 считывает максимальное значение каждый раз через l oop. Версия -O1 считывает его вне l oop один раз.

-O0

08000208 <L6.8>:
 8000208:   9800        ldr r0, [sp, #0]
 800020a:   3001        adds    r0, #1
 800020c:   9000        str r0, [sp, #0]
 800020e:   4902        ldr r1, [pc, #8]    ; (8000218 <L6.24>)
 8000210:   9800        ldr r0, [sp, #0]
 8000212:   4288        cmp r0, r1
 8000214:   d3f8        bcc.n   8000208 <L6.8>

1200011/100000 = 12

Большая часть времени находится в вышеупомянутое l oop. 7 инструкций три груза два магазина. Это 12 вещей, так что, возможно, его один такт на.

-O1 код

0800020a <L6.10>:
 800020a:   9900        ldr r1, [sp, #0]
 800020c:   3101        adds    r1, #1
 800020e:   9100        str r1, [sp, #0]
08000210 <L6.16>:
 8000210:   9900        ldr r1, [sp, #0]
 8000212:   4281        cmp r1, r0
 8000214:   d3f9        bcc.n   800020a <L6.10>

1000014/100000 = 10

0800020a <L6.10>:
 800020a:   9900        ldr r1, [sp, #0]
 800020c:   3101        adds    r1, #1
 800020e:   9100        str r1, [sp, #0]
 8000210:   9900        ldr r1, [sp, #0]
 8000212:   4281        cmp r1, r0
 8000214:   d3f9        bcc.n   800020a <L6.10>

6 инструкций, две загрузки в одном магазине , 8 вещей 10 часов. Отличие от -O0 заключается в том, что сравниваемое значение считывается до / вне l oop, что позволяет сохранить эту инструкцию и этот цикл памяти.

-O2 код

08000208 <L4.8>:
 8000208:   9900        ldr r1, [sp, #0]
 800020a:   3101        adds    r1, #1
 800020c:   9100        str r1, [sp, #0]
 800020e:   4281        cmp r1, r0
 8000210:   d3fa        bcc.n   8000208 <L4.8>

700005 / 100000 = 7 тиков за л oop

Так, по определению некоторых людей, это не соблюдает изменчивость или нет? Значение сравнения находится вне l oop, и, как написано, должно быть 2000000 + 1, да? Он считывает i из памяти один раз за l oop, а не дважды, но сохраняет его каждый раз через l oop с новым значением. В основном он удалил вторую загрузку, и это сэкономило время ожидания при чтении до конечного кода sh.

-O3

08000208 <L5.8>:
 8000208:   9900        ldr r1, [sp, #0]
 800020a:   3101        adds    r1, #1
 800020c:   9100        str r1, [sp, #0]
 800020e:   4281        cmp r1, r0
 8000210:   d3fa        bcc.n   8000208 <L5.8>

Внутренний l oop такой же, как - O2.

-O2 делает это

08000200 <soft_delay>:
 8000200:   b081        sub sp, #4
 8000202:   2000        movs    r0, #0
 8000204:   9000        str r0, [sp, #0]
 8000206:   4804        ldr r0, [pc, #16]   ; (8000218 <L4.24>)
...
 8000212:   b001        add sp, #4
 8000214:   4770        bx  lr

-O3 делает это

08000200 <soft_delay>:
 8000200:   b508        push    {r3, lr}
 8000202:   2000        movs    r0, #0
 8000204:   9000        str r0, [sp, #0]
 8000206:   4803        ldr r0, [pc, #12]   ; (8000214 <L5.20>)

 8000212:   bd08        pop {r3, pc}

Теперь инструкций меньше, да, но pu sh и pop take чем дольше они занимают циклы памяти, тем меньше операций вычитания и сложения указателя стека быстрее, чем при меньшем количестве инструкций. Так что тонкая разница во времени - это толчок / треск за пределами l oop.

Теперь для G CC (9.2.0)

Для начала я не знаю, был ли нацелен на киль в общем (все варианты) cortex-ms или cortex-m3 в частности.

Первый код -O0:

-O0

soft_delay:
    push    {r7, lr}
    sub sp, sp, #8
    add r7, sp, #0
    movs    r3, #0
    str r3, [r7, #4]
    b   .L2
.L3:
    ldr r3, [r7, #4]
    adds    r3, r3, #1
    str r3, [r7, #4]
.L2:
    ldr r3, [r7, #4]
    ldr r2, .L4
    cmp r3, r2
    bls .L3
    nop
    nop
    mov sp, r7
    add sp, sp, #8
    @ sp needed
    pop {r7}
    pop {r0}
    bx  r0
.L5:
    .align  2
.L4:
    .word   199999

08000200 <soft_delay>:
 8000200:   b580        push    {r7, lr}
 8000202:   b082        sub sp, #8
 8000204:   af00        add r7, sp, #0
 8000206:   2300        movs    r3, #0
 8000208:   607b        str r3, [r7, #4]
 800020a:   e002        b.n 8000212 <soft_delay+0x12>
 800020c:   687b        ldr r3, [r7, #4]
 800020e:   3301        adds    r3, #1
 8000210:   607b        str r3, [r7, #4]
 8000212:   687b        ldr r3, [r7, #4]
 8000214:   4a04        ldr r2, [pc, #16]   ; (8000228 <soft_delay+0x28>)
 8000216:   4293        cmp r3, r2
 8000218:   d9f8        bls.n   800020c <soft_delay+0xc>
 800021a:   46c0        nop         ; (mov r8, r8)
 800021c:   46c0        nop         ; (mov r8, r8)
 800021e:   46bd        mov sp, r7
 8000220:   b002        add sp, #8
 8000222:   bc80        pop {r7}
 8000224:   bc01        pop {r0}
 8000226:   4700        bx  r0
 8000228:   00030d3f    andeq   r0, r3, pc, lsr sp

00124F9F 

сразу же мы видим две вещи: во-первых, кадр стека, который Киль не строил, и во-вторых, эти загадочные гайки после сравнения. ошибка чипа или что-то, нужно искать это. Из моего другого ответа, который к настоящему времени может быть удален, g cc 5.4.0 положил один nop, t cc 9.2.0 положил два. так что этот l oop имеет

1200031/100000 = 12 тиков на л oop

 800020c:   687b        ldr r3, [r7, #4]
 800020e:   3301        adds    r3, #1
 8000210:   607b        str r3, [r7, #4]
 8000212:   687b        ldr r3, [r7, #4]
 8000214:   4a04        ldr r2, [pc, #16]   ; (8000228 <soft_delay+0x28>)
 8000216:   4293        cmp r3, r2
 8000218:   d9f8        bls.n   800020c <soft_delay+0xc>

Основной l oop, где этот код тратит свое время, также равен 12 тикам Киль это же просто разные регистры, которые не имеют значения. Тонкая общая разница во времени заключается в том, что кадр стека и дополнительные nops делают версию g cc длиннее.

arm-none-eabi-gcc -O0 -fomit-frame-pointer -c -mthumb -mcpu=cortex-m0 hello.c -o hello.o
arm-none-eabi-objdump -D hello.o > hello.list
arm-none-eabi-gcc -O0 -fomit-frame-pointer -S -mthumb -mcpu=cortex-m0 hello.c

Если я строю без указателя кадра, тогда g cc -O0 становится

soft_delay:
    sub sp, sp, #8
    movs    r3, #0
    str r3, [sp, #4]
    b   .L2
.L3:
    ldr r3, [sp, #4]
    adds    r3, r3, #1
    str r3, [sp, #4]
.L2:
    ldr r3, [sp, #4]
    ldr r2, .L4
    cmp r3, r2
    bls .L3
    nop
    nop
    add sp, sp, #8
    bx  lr
.L5:
    .align  2
.L4:
    .word   99999

08000200 <soft_delay>:
 8000200:   b082        sub sp, #8
 8000202:   2300        movs    r3, #0
 8000204:   9301        str r3, [sp, #4]
 8000206:   e002        b.n 800020e <soft_delay+0xe>
 8000208:   9b01        ldr r3, [sp, #4]
 800020a:   3301        adds    r3, #1
 800020c:   9301        str r3, [sp, #4]
 800020e:   9b01        ldr r3, [sp, #4]
 8000210:   4a03        ldr r2, [pc, #12]   ; (8000220 <soft_delay+0x20>)
 8000212:   4293        cmp r3, r2
 8000214:   d9f8        bls.n   8000208 <soft_delay+0x8>
 8000216:   46c0        nop         ; (mov r8, r8)
 8000218:   46c0        nop         ; (mov r8, r8)
 800021a:   b002        add sp, #8
 800021c:   4770        bx  lr
 800021e:   46c0        nop         ; (mov r8, r8)
 8000220:   0001869f

 00124F94

и сохраняет 11 часов по сравнению с другой версией g cc в отличие от Kiel g cc не выполняет функцию pop sh pop, поэтому экономит некоторые часы над Kiel, но шуты не помогают.

Да, верно, и у меня было неправильное количество циклов для Киля, потому что он использовал беззнаковое нижнее вместо беззнакового нижнего или такое же, как с g cc. Даже на игровом поле, удалите nops, исправьте петли, g cc - 00124F92, а Kiel 00124F97 - на 5 часов медленнее из-за математики «пуш / поп» и «сп». g cc 5.4.0 также выполняет sp math, с nop 00124F93. Находясь за пределами l oop, эти различия, в то же время измеримые, также заключаются в шуме при сравнении этих двух (трех) компиляторов.

g cc -O1

soft_delay:
    sub sp, sp, #8
    mov r3, #0
    str r3, [sp, #4]
    ldr r2, [sp, #4]
    ldr r3, .L5
    cmp r2, r3
    bhi .L1
    mov r2, r3
.L3:
    ldr r3, [sp, #4]
    add r3, r3, #1
    str r3, [sp, #4]
    ldr r3, [sp, #4]
    cmp r3, r2
    bls .L3
.L1:
    add sp, sp, #8
    bx  lr
.L6:
    .align  2
.L5:
    .word   99999

08000200 <soft_delay>:
 8000200:   b082        sub sp, #8
 8000202:   2300        movs    r3, #0
 8000204:   9301        str r3, [sp, #4]
 8000206:   9a01        ldr r2, [sp, #4]
 8000208:   4b05        ldr r3, [pc, #20]   ; (8000220 <soft_delay+0x20>)
 800020a:   429a        cmp r2, r3
 800020c:   d806        bhi.n   800021c <soft_delay+0x1c>
 800020e:   1c1a        adds    r2, r3, #0
 8000210:   9b01        ldr r3, [sp, #4]
 8000212:   3301        adds    r3, #1
 8000214:   9301        str r3, [sp, #4]
 8000216:   9b01        ldr r3, [sp, #4]
 8000218:   4293        cmp r3, r2
 800021a:   d9f9        bls.n   8000210 <soft_delay+0x10>
 800021c:   b002        add sp, #8
 800021e:   4770        bx  lr
 8000220:   0001869f    muleq   r1, pc, r6  ; <UNPREDICTABLE>

000F4251

10 тиков в l oop

 8000210:   9b01        ldr r3, [sp, #4]
 8000212:   3301        adds    r3, #1
 8000214:   9301        str r3, [sp, #4]
 8000216:   9b01        ldr r3, [sp, #4]
 8000218:   4293        cmp r3, r2
 800021a:   d9f9        bls.n   8000210 <soft_delay+0x10>

То же, что и в Киле, нагрузка сравниваемого значения находится за пределами l oop, сохраняя при этом чуть-чуть на l oop. Это было спроектировано немного по-другому. И я верю, что nops после BLS - это нечто другое. Просто увидел, как кто-то спрашивает, почему g cc сделал что-то, что другой не сделал, что казалось дополнительной инструкцией. Я бы использовал термин «пропущенная оптимизация против ошибки», но в любом случае у этого нет nops ...

g cc -O2 код

soft_delay:
    mov r3, #0
    sub sp, sp, #8
    str r3, [sp, #4]
    ldr r3, [sp, #4]
    ldr r2, .L7
    cmp r3, r2
    bhi .L1
.L3:
    ldr r3, [sp, #4]
    add r3, r3, #1
    str r3, [sp, #4]
    ldr r3, [sp, #4]
    cmp r3, r2
    bls .L3
.L1:
    add sp, sp, #8
    bx  lr
.L8:
    .align  2
.L7:
    .word   99999

08000200 <soft_delay>:
 8000200:   2300        movs    r3, #0
 8000202:   b082        sub sp, #8
 8000204:   9301        str r3, [sp, #4]
 8000206:   9b01        ldr r3, [sp, #4]
 8000208:   4a05        ldr r2, [pc, #20]   ; (8000220 <soft_delay+0x20>)
 800020a:   4293        cmp r3, r2
 800020c:   d805        bhi.n   800021a <soft_delay+0x1a>
 800020e:   9b01        ldr r3, [sp, #4]
 8000210:   3301        adds    r3, #1
 8000212:   9301        str r3, [sp, #4]
 8000214:   9b01        ldr r3, [sp, #4]
 8000216:   4293        cmp r3, r2
 8000218:   d9f9        bls.n   800020e <soft_delay+0xe>
 800021a:   b002        add sp, #8
 800021c:   4770        bx  lr
 800021e:   46c0        nop         ; (mov r8, r8)
 8000220:   0001869f

000F4251

Нет отличий от -O1

 800020e:   9b01        ldr r3, [sp, #4]
 8000210:   3301        adds    r3, #1
 8000212:   9301        str r3, [sp, #4]
 8000214:   9b01        ldr r3, [sp, #4]
 8000216:   4293        cmp r3, r2
 8000218:   d9f9        bls.n   800020e <soft_delay+0xe>

g cc не желает брать эту вторую нагрузку из l oop.

на уровне -O2 Киль - 70005 тиков и g cc 1000017. На 42 процента больше / медленнее.

g cc -O3 дал тот же код, что и -O2.

Так что ключевое отличие здесь, возможно, заключается в интерпретации того, что делает volatile, и есть некоторые люди в SO, которые все равно расстраиваются по поводу ее использования, но давайте просто предположим, что это означает, что все, что вы делаете с переменной, должно go в / из памяти.

Из того, что я обычно вижу, это означает

.L3:
    ldr r3, [sp, #4]
    add r3, r3, #1
    str r3, [sp, #4]
    ldr r3, [sp, #4]
    cmp r3, r2
    bls .L3

не это

.L3:
    ldr r3, [sp, #4]
    add r3, r3, #1
    str r3, [sp, #4]
    cmp r3, r2
    bls .L3

Это ошибка Киля? Вы хотите использовать здесь термин сверхоптимизации?

Существует две операции: приращение

    ldr r3, [sp, #4]
    add r3, r3, #1
    str r3, [sp, #4]

и сравнение

    ldr r3, [sp, #4]
    cmp r3, r2
    bls .L3

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

Когда вы выясняете, какой у вас g cc и как он использовался может учитывать еще больше кода на стороне g cc, что на 100% медленнее, а не на 40%.

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

ааа, это была пропущенная оптимизация в g cc

cmp r3, r2
bhi .L1

g cc знал, что оно начинается с нуля, и знал, что оно будет больше, поэтому r3 здесь никогда не будет больше, чем r2.

Мы будем sh для инструмента, который сделает это

soft_delay:
    mov r3, #0
    ldr r2, .L7
.L3:
    add r3, r3, #1
    cmp r3, r2
    bls .L3
.L1:
    bx  lr
.L8:
    .align  2
.L7:
    .word   99999

00061A88

at 4 instructions per loop on average

, но без изменяемого кода это мертвый код, поэтому оптимизатор просто удалит его, а не создаст этот код. Отсчет l oop будет немного меньше

soft_delay:
    ldr r2, .L7
.L3:
    sub r2, r2, #1
    bne .L3
.L1:
    bx  lr
.L8:
    .align  2
.L7:
    .word   100000

000493E7

3 такта на л oop, удаление дополнительной инструкции помогло.

Чрезмерная оптимизация Keil (маловероятно, потому что код очень прост)

Вы действительно можете быть здесь, не потому, что он прост, но что действительно означает volatile и подлежит ли оно интерпретации компиляторами (мне пришлось бы найти спецификация c). Это ошибка Киля, не слишком ли оптимизирована?

По-прежнему нет такой вещи, как чрезмерная оптимизация, есть имя для этого, ошибка компилятора. Так же, как Киль истолковал это неправильно, или Киль и g cc не согласились с интерпретацией volatile.

недоптимизация arm-none-eabi-g cc из-за неправильных флагов компилятора (я использую CLion Embedded plugins` CMakeLists.txt)

Это может быть также и по той же причине. Является ли это просто «определенной реализацией» разницей между компиляторами, и оба они правы, основываясь на их определении?

Теперь g cc пропустил оптимизацию здесь (или две), но она составляет небольшую сумму, так как находится за пределами л oop.

0 голосов
/ 26 января 2020

                 GCC              |||                  KEIL
                                  |||
soft_delay:                       |||
        mov     r3, #0            |||
        sub     sp, sp, #8        |||
        str     r3, [sp, #4]      |||
        ldr     r3, [sp, #4]      |||
        ldr     r2, .L7           |||
        cmp     r3, r2            |||
        bhi     .L1               |||        soft_delay PROC
.L3:                              |||                PUSH     {r3,lr}
        ldr     r3, [sp, #4]      |||                MOVS     r0,#0     
        add     r3, r3, #1        |||                STR      r0,[sp,#0]
        str     r3, [sp, #4]      |||                LDR      r0,|L5.20|
        ldr     r3, [sp, #4]      |||        |L5.8|
        cmp     r3, r2            |||                LDR      r1,[sp,#0]
        bls     .L3               |||                ADDS     r1,r1,#1
.L1:                              |||                STR      r1,[sp,#0]
        add     sp, sp, #8        |||                CMP      r1,r0
        bx      lr                |||                BCC      |L5.8|
.L7:                              |||                POP      {r3,pc}
        .word   1999999           |||                ENDP               

В KEIL есть очевидная ошибка. volatile означает, что его значение необходимо загружать перед каждым использованием и сохранять при изменении. ? Кейлу не хватает одного груза.

Переменная используется 2 раза: 1: при увеличении, 2: при сравнении. Требуются две нагрузки.

...