Die exakte Kopie des Maschinencodes läuft 50% langsamer als die ursprüngliche Funktion

11

Ich habe ein bisschen mit der Ausführung von RAM und Flash-Speicher auf eingebetteten Systemen experimentiert. Für Rapid Prototyping und Tests verwende ich derzeit einen Arduino Due (SAM3X8E ARM Cortex-M3). Soweit ich sehen kann, sollten die Arduino-Laufzeit und der Bootloader hier keinen Unterschied machen.

Hier ist das Problem: Ich habe eine Funktion ( calc ), die in ARM Thumb Assembly geschrieben ist. calc berechnet eine Zahl und gibt sie zurück. (> 1s Laufzeit für die angegebene Eingabe) Jetzt habe ich den zusammengestellten Maschinencode dieser Funktion manuell extrahiert und als Rohbytes in eine andere Funktion eingefügt. Es wurde bestätigt, dass sich beide Funktionen im Flash-Speicher befinden (Adresse 0x80149 und 0x8017D, direkt nebeneinander). Dies wurde sowohl durch Demontage als auch durch eine Laufzeitprüfung bestätigt.

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() {

}

Die Ausgabe des obigen Programms auf dem Arduino Due-Ziel ist:

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

Daher bestätigen wir, dass die Ergebnisse gleich sind und die Adresse zur Laufzeit wie erwartet ist. Die Ausführung der manuell eingegebenen Maschinencodefunktion ist 50% langsamer.

Die Demontage mit arm-none-eabi-objdump bestätigt ferner die jeweiligen Adressen, die Flash-Speicherresidenz und die Gleichheit des Maschinencodes (Endianness und Byte-Gruppierung beachten!):

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
    ...

Wir können die analog verwendete Aufrufkonvention weiter bestätigen:

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

Ich kann ausschließen, dass dies auf eine Art spekulativen Abruf (den der Cortex-M3 anscheinend hat!) Oder auf Interrupts zurückzuführen ist. (EDIT: NOPE, ich kann nicht. Wahrscheinlich eine Art Prefetch.) Das Ändern der Ausführungsreihenfolge oder das Hinzufügen von Funktionsaufrufen dazwischen ändert nichts am Ergebnis. Was könnte der Schuldige hier sein?


BEARBEITEN: Nach dem Ändern der Ausrichtung der Maschinencodefunktion (Nops als Prolog einfügen) erhalte ich die folgenden Ergebnisse:

+ 16bit für calc2:

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

+ 32bit für calc2:

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

+ 48bit für calc2:

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

+ 64bit für calc2:

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

+ 80bit für calc2:

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

EDIT2: Nur calc ausführen:

Address: 524617 Res: 3524578: 1102155us

Läuft nur calc2:

Address: 524617 Res: 3524578: 1102257us

Reihenfolge ändern:

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

EDIT3: Hinzufügen .p2align 4vor Label nur .infür Calc, separate Ausführung:

Address: 524625 Res: 3524578: 1413185us

Beides wie im ursprünglichen Benchmark:

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

EDIT4: Das Umkehren der Position im Blitz ändert das Ergebnis vollständig. -> Linearer Prefetch?

fscheidl
quelle
Kommentare sind nicht für eine ausführliche Diskussion gedacht. Dieses Gespräch wurde in den Chat verschoben .
Samuel Liew

Antworten:

4

Die Geschwindigkeit der Codeausführung von Flash hängt von der Anzahl der Wartezyklen und der Codeausrichtung für jedes Verzweigungsziel ab. In diesem und ähnlichen Prozessoren wie STM32F103 benötigt Flash 3 Wartezyklen, wenn der Kern mit der höchsten Frequenz ausgeführt wird. Dies bedeutet, dass jeder genommene Zweig zwischen 2 und 5 Zyklen dauern kann, was sich auf die Gesamtlaufzeit auswirken kann.

Um die FLASH-Langsamkeit auszugleichen, verfügen diese Prozessoren über einen breiten FLASH-Bus und einen Abrufpuffer. SAM3X verfügt über ein Paar 128-Bit-Befehlspuffer, die scheinbar in einem Prefetch-Muster gefüllt sind [1].

Um eine enge Schleife zu optimieren, versuchen Sie, in einen 32-Byte-Codeblock zu passen und ihn an der 16-Byte-Grenze auszurichten (oder besser 32, nur für den Fall). Es kann auch eine gute Idee sein, zu überprüfen, ob die FLASH-Parameter in dieser MCU korrekt eingerichtet sind, dh der Vorabruf ist aktiviert und die Busbreite auf 128 Bit eingestellt. Das Kopieren von Code in den Arbeitsspeicher ist möglicherweise eine Option, aber es ist schmerzhaft und kann die Dinge im Vergleich zu ordnungsgemäß funktionierenden Abrufpuffern verlangsamen.

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

AK
quelle