La copia esatta del codice macchina è più lenta del 50% rispetto alla funzione originale


11

Ho sperimentato un po 'l'esecuzione da RAM e memoria flash su sistemi embedded. Per la prototipazione rapida e il test sto attualmente utilizzando un Arduino Due (SAM3X8E ARM Cortex-M3). Per quanto posso vedere, il runtime e il bootloader di Arduino non dovrebbero fare differenza qui.

Ecco il problema: ho una funzione ( calc ) che è scritta in ARM Thumb Assembly. calc calcola un numero e lo restituisce. (> 1 runtime per il dato input) Ora ho estratto manualmente il codice macchina assemblato di quella funzione e l'ho messo come byte grezzi in un'altra funzione. Si conferma che entrambe le funzioni risiedono nella memoria flash (indirizzo 0x80149 e 0x8017D, uno accanto all'altro). Ciò è stato confermato sia tramite lo smontaggio che un controllo di runtime.

void setup() {
  Serial.begin(115200);
  timeFnc(calc);
  timeFnc(calc2);
}

void timeFnc(int (*functionPtr)(void)) {
  unsigned long time1 = micros();

  int res = (*functionPtr)();

  unsigned long time2 = micros();
  Serial.print("Address: ");
  Serial.print((unsigned int)functionPtr);
  Serial.print(" Res: ");
  Serial.print(res);
  Serial.print(": ");
  Serial.print(time2-time1);
  Serial.println("us");

}

int calc() {
   asm volatile(
      "movs r1, #33 \n\t"
      "push {r1,r4,r5,lr} \n\t"
      "bl .in \n\t"
      "pop {r1,r4,r5,lr} \n\t"
      "bx lr \n\t"

      ".in: \n\t"
      "movs r5,#1 \n\t"
      "subs r1, r1, #1 \n\t"
      "cmp r1, #2 \n\t"
      "blo .lblb \n\t"
      "movs r5,#1 \n\t"

      ".lbla: \n\t"
      "push {r1, r5, lr} \n\t"
      "bl .in \n\t"
      "pop {r1, r5, lr} \n\t"
      "adds r5,r0 \n\t"
      "subs r1,#2 \n\t"
      "cmp r1,#1 \n\t"
      "bhi .lbla \n\t"
      ".lblb: \n\t"
      "movs r0,r5 \n\t"
      "bx lr \n\t"
      ::
   ); //redundant auto generated bx lr, aware of that
}

int calc2() {
  asm volatile(
    ".word  0xB5322121 \n\t"
    ".word  0xF803F000 \n\t"
    ".word  0x4032E8BD \n\t"
    ".word  0x25014770 \n\t"

    ".word  0x29023901 \n\t"
    ".word  0x800BF0C0 \n\t"
    ".word  0xB5222501 \n\t"
    ".word  0xFFF7F7FF \n\t"
    ".word  0x4022E8BD \n\t"
    ".word  0x3902182D \n\t"
    ".word  0xF63F2901 \n\t"
    ".word  0x0028AFF6 \n\t"
    ".word  0x47704770 \n\t"
  );
}

void loop() {

}

L'output del programma sopra sul target di Arduino Due è:

Address: 524617 Res: 3524578: 1338254us
Address: 524669 Res: 3524578: 2058819us

Quindi confermiamo che i risultati sono uguali e l'indirizzo durante il runtime deve essere come previsto. L'esecuzione della funzione del codice macchina inserita manualmente è più lenta del 50%.

Lo smontaggio con arm-none-eabi-objdump conferma ulteriormente i rispettivi indirizzi, la residenza della memoria flash e l'uguaglianza del codice macchina (Nota endianness e raggruppamento di byte!):

00080148 <_Z4calcv>:
   80148:   2121        movs    r1, #33 ; 0x21
   8014a:   b532        push    {r1, r4, r5, lr}
   8014c:   f000 f803   bl  80156 <.in>
   80150:   e8bd 4032   ldmia.w sp!, {r1, r4, r5, lr}
   80154:   4770        bx  lr

00080156 <.in>:
   80156:   2501        movs    r5, #1
   80158:   3901        subs    r1, #1
   8015a:   2902        cmp r1, #2
   8015c:   f0c0 800b   bcc.w   80176 <.lblb>
   80160:   2501        movs    r5, #1

00080162 <.lbla>:
   80162:   b522        push    {r1, r5, lr}
   80164:   f7ff fff7   bl  80156 <.in>
   80168:   e8bd 4022   ldmia.w sp!, {r1, r5, lr}
   8016c:   182d        adds    r5, r5, r0
   8016e:   3902        subs    r1, #2
   80170:   2901        cmp r1, #1
   80172:   f63f aff6   bhi.w   80162 <.lbla>

00080176 <.lblb>:
   80176:   0028        movs    r0, r5
   80178:   4770        bx  lr
}
   8017a:   4770        bx  lr

0008017c <_Z5calc2v>:
   8017c:   b5322121    .word   0xb5322121
   80180:   f803f000    .word   0xf803f000
   80184:   4032e8bd    .word   0x4032e8bd
   80188:   25014770    .word   0x25014770
   8018c:   29023901    .word   0x29023901
   80190:   800bf0c0    .word   0x800bf0c0
   80194:   b5222501    .word   0xb5222501
   80198:   fff7f7ff    .word   0xfff7f7ff
   8019c:   4022e8bd    .word   0x4022e8bd
   801a0:   3902182d    .word   0x3902182d
   801a4:   f63f2901    .word   0xf63f2901
   801a8:   0028aff6    .word   0x0028aff6
   801ac:   47704770    .word   0x47704770
}
   801b0:   4770        bx  lr
    ...

Possiamo ulteriormente confermare la convenzione di chiamata utilizzata in modo analogo:

00080234 <setup>:
void setup() {
   80234:   b508        push    {r3, lr}
  Serial.begin(115200);
   80236:   4806        ldr r0, [pc, #24]   ; (80250 <setup+0x1c>)
   80238:   f44f 31e1   mov.w   r1, #115200 ; 0x1c200
   8023c:   f000 fcb4   bl  80ba8 <_ZN9UARTClass5beginEm>
  timeFnc(calc);
   80240:   4804        ldr r0, [pc, #16]   ; (80254 <setup+0x20>)
   80242:   f7ff ffb7   bl  801b4 <_Z7timeFncPFivE>
}
   80246:   e8bd 4008   ldmia.w sp!, {r3, lr}
  timeFnc(calc2);
   8024a:   4803        ldr r0, [pc, #12]   ; (80258 <setup+0x24>)
   8024c:   f7ff bfb2   b.w 801b4 <_Z7timeFncPFivE>
   80250:   200705cc    .word   0x200705cc
   80254:   00080149    .word   0x00080149
   80258:   0008017d    .word   0x0008017d

Posso escludere questo fatto a causa di una sorta di recupero speculativo (che apparentemente Cortex-M3 ha!) O interrompe. (EDIT: NOPE, non posso. Probabilmente una sorta di prefetch) La modifica dell'ordine di esecuzione o l'aggiunta di chiamate di funzione in mezzo non modifica il risultato. Quale potrebbe essere il colpevole qui?


EDIT: dopo aver modificato l'allineamento della funzione del codice macchina (inserire nops come prologo) ottengo i seguenti risultati:

+ 16 bit per calc2:

Address: 524617 Res: 3524578: 1102257us
Address: 524669 Res: 3524578: 1846968us

+ 32 bit per calc2:

Address: 524617 Res: 3524578: 1102257us
Address: 524669 Res: 3524578: 1535424us

+ 48 bit per calc2:

Address: 524617 Res: 3524578: 1102155us
Address: 524669 Res: 3524578: 1413180us

+ 64 bit per calc2:

Address: 524617 Res: 3524578: 1102155us
Address: 524669 Res: 3524578: 1346606us

+ 80 bit per calc2:

Address: 524617 Res: 3524578: 1102145us
Address: 524669 Res: 3524578: 1180105us

EDIT2: esegue solo calc:

Address: 524617 Res: 3524578: 1102155us

Eseguendo solo calc2:

Address: 524617 Res: 3524578: 1102257us

Modifica dell'ordine:

Address: 524669 Res: 3524578: 1554160us
Address: 524617 Res: 3524578: 1102211us

EDIT3: aggiunta .p2align 4prima dell'etichetta .insolo per calc, esecuzione separata:

Address: 524625 Res: 3524578: 1413185us

Sia come nel benchmark originale:

Address: 524625 Res: 3524578: 1413185us
Address: 524689 Res: 3524578: 1535424us

EDIT4: l'inversione della posizione nel flash cambia completamente il risultato. -> Prefetch lineare?


I commenti non sono per una discussione estesa; questa conversazione è stata spostata in chat .
Samuel Liew

Risposte:


4

La velocità di esecuzione del codice da flash dipende dal numero di cicli di attesa e dall'allineamento del codice per ciascun target di diramazione. In questo e processori simili, come STM32F103, il flash necessita di 3 cicli di attesa quando il core funziona alla massima frequenza. Ciò significa che ogni ramo preso può richiedere tra 2 e 5 cicli, il che potrebbe influire sul tempo di esecuzione totale.

Per compensare la lentezza FLASH, questi processori hanno un bus FLASH ampio e un buffer di recupero. SAM3X ha una coppia di buffer di istruzioni a 128 bit, che sembrano essere popolati in un modello di prefetch [1].

Per ottimizzare un circuito stretto, prova a inserirti in un blocco di codice a 32 byte e allinearlo al limite di 16 byte (o meglio 32, per ogni evenienza). Inoltre, potrebbe essere una buona idea verificare se i parametri FLASH sono impostati correttamente, ovvero prefetch è abilitato e la larghezza del bus è impostata su 128 bit, in questo MCU. La copia del codice nella RAM può essere un'opzione, ma è una seccatura e può effettivamente rallentare le cose, rispetto ai buffer di recupero che funzionano correttamente.

[1] http://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-11057-32-bit-Cortex-M3-Microcontroller-SAM3X-SAM3A_Datasheet.pdf , pagina 294, Figure 18-2, 18-3 .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.