Salinan kode mesin yang tepat berjalan 50% lebih lambat dari fungsi aslinya

11

Saya telah bereksperimen sedikit dengan eksekusi dari RAM dan memori flash pada sistem embedded. Untuk prototyping dan pengujian cepat, saya saat ini menggunakan Arduino Due (SAM3X8E ARM Cortex-M3). Sejauh yang saya bisa lihat, runtime dan bootloader Arduino seharusnya tidak membuat perbedaan di sini.

Inilah masalahnya: Saya memiliki fungsi ( calc ) yang ditulis dalam ARM Thumb Assembly. calc menghitung angka dan mengembalikannya. (> 1s runtime untuk input yang diberikan) Sekarang saya secara manual mengekstraksi kode mesin rakitan dari fungsi itu dan meletakkannya sebagai byte mentah ke dalam fungsi lain. Kedua fungsi dikonfirmasi untuk berada dalam memori flash (Alamat 0x80149 dan 0x8017D, tepat di sebelah satu sama lain). Ini telah dikonfirmasi baik melalui pembongkaran dan pemeriksaan 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() {

}

Output dari program di atas pada target Arduino Due adalah:

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

Jadi kami mengkonfirmasi hasilnya sama dan alamat saat runtime sesuai dengan yang diharapkan. Eksekusi fungsi kode mesin yang dimasukkan secara manual 50% lebih lambat.

Pembongkaran dengan arm-none-eabi-objdump selanjutnya mengonfirmasi masing-masing alamat, memori flash, dan kesetaraan kode mesin (Perhatikan pengelompokan endianness dan 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
    ...

Kami selanjutnya dapat mengkonfirmasi konvensi panggilan yang digunakan secara analog:

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

Saya dapat mengesampingkan makhluk ini karena pengambilan spekulatif (yang tampaknya dimiliki Cortex-M3!) Atau menyela. (EDIT: NOPE, saya tidak bisa. Mungkin semacam prefetch) Mengubah urutan eksekusi atau menambahkan fungsi panggilan di antara tidak mengubah hasilnya. Apa yang bisa menjadi pelakunya di sini?


EDIT: Setelah mengubah perataan fungsi kode mesin (masukkan nops sebagai prolog) saya mendapatkan hasil berikut:

+ 16bit untuk calc2:

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

+ 32bit untuk calc2:

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

+ 48bit untuk calc2:

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

+ 64bit untuk calc2:

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

+ 80bit untuk calc2:

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

EDIT2: Hanya menjalankan calc:

Address: 524617 Res: 3524578: 1102155us

Hanya menjalankan calc2:

Address: 524617 Res: 3524578: 1102257us

Mengubah urutan:

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

EDIT3: Menambahkan .p2align 4sebelum label .inhanya untuk calc, eksekusi terpisah:

Address: 524625 Res: 3524578: 1413185us

Keduanya seperti dalam tolok ukur asli:

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

EDIT4: Membalik posisi dalam flash benar-benar mengubah hasilnya. -> prefetch Linear?

fscheidl
sumber
Komentar bukan untuk diskusi panjang; percakapan ini telah dipindahkan ke obrolan .
Samuel Liew

Jawaban:

4

Kecepatan eksekusi kode dari flash tergantung pada jumlah siklus tunggu dan perataan kode untuk setiap target cabang. Dalam prosesor ini dan yang serupa, seperti STM32F103, flash membutuhkan 3 siklus tunggu ketika core berjalan pada frekuensi tertinggi. Ini berarti setiap cabang yang diambil dapat memakan waktu antara 2 dan 5 siklus, yang mungkin memengaruhi total jangka waktu.

Untuk mengimbangi kelambatan FLASH, prosesor ini memiliki FLASH bus yang lebar dan buffer pengambilan. SAM3X memiliki sepasang buffer instruksi 128-bit, yang tampaknya diisi dalam pola prefetch [1].

Untuk mengoptimalkan perulangan yang ketat, coba paskan ke dalam blok kode 32-byte dan luruskan pada batas 16-byte (atau lebih baik 32, untuk berjaga-jaga). Juga, itu bisa menjadi ide yang baik untuk memeriksa apakah parameter FLASH diatur dengan benar, yaitu prefetch diaktifkan dan lebar bus diatur ke 128 bit, di MCU ini. Menyalin kode ke RAM mungkin menjadi pilihan, tapi itu menyebalkan dan sebenarnya bisa memperlambat segalanya, dibandingkan dengan bekerja dengan benar mengambil buffer.

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

AK
sumber