기계 코드의 정확한 사본은 원래 기능보다 50 % 느리게 실행됩니다


11

임베디드 시스템의 RAM 및 플래시 메모리에서 실행하는 것에 대해 약간 실험했습니다. 빠른 프로토 타이핑 및 테스트를 위해 현재 Arduino Due (SAM3X8E ARM Cortex-M3)를 사용하고 있습니다. 내가 아는 한, Arduino 런타임과 부트 로더는 여기서 아무런 차이가 없습니다.

문제는 다음과 같습니다 . ARM Thumb 어셈블리로 작성된 함수 ( calc )가 있습니다. calc 는 숫자를 계산하여 반환합니다. (주어진 입력에 대해 1 초 이상의 런타임) 이제 해당 함수의 조립 된 기계 코드를 수동으로 추출하여 원시 바이트로 다른 함수에 넣었습니다. 두 기능 모두 플래시 메모리 (주소 0x80149 및 0x8017D, 바로 옆)에 상주합니다. 이것은 분해와 런타임 확인을 통해 확인되었습니다.

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

}

Arduino Due 대상에서 위 프로그램의 출력은 다음과 같습니다.

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

따라서 결과가 같고 런타임 동안 주소가 예상대로인지 확인합니다. 수동으로 입력 한 기계 코드 기능의 실행이 50 % 느립니다.

arm-none-eabi-objdump를 사용하여 디스 어셈블리하면 해당 주소, 플래시 메모리 상주 및 머신 코드의 동등성을 확인합니다 (엔디안 및 바이트 그룹화 참고).

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

유사하게 사용되는 호출 규칙을 추가로 확인할 수 있습니다.

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

나는 일종의 추론 적 인출 (Cortex-M3이 겉보기에 가지고 있음!) 또는 인터럽트로 인해 이것을 배제 할 수 있습니다. (편집 : NOPE, 불가능합니다. 아마도 어떤 종류의 프리 페치 일 것입니다) 실행 순서를 변경하거나 사이에 함수 호출을 추가해도 결과는 변경되지 않습니다. 여기서 범인은 무엇입니까?


편집 : 기계 코드 기능의 정렬을 변경 한 후 (프롤로그로 nops 삽입) 다음과 같은 결과가 나타납니다.

calc2의 경우 +16 비트 :

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

calc2의 경우 +32 비트 :

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

calc2의 경우 +48 비트 :

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

calc2의 경우 +64 비트 :

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

calc2의 경우 +80 비트 :

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

EDIT2 : calc 만 실행 :

Address: 524617 Res: 3524578: 1102155us

calc2 만 실행 :

Address: 524617 Res: 3524578: 1102257us

순서 변경 :

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

EDIT3 : calc 전용 .p2align 4레이블 앞에 추가 .in, 별도 실행 :

Address: 524625 Res: 3524578: 1413185us

원래 벤치 마크에서와 같이 :

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

EDIT4 : 플래시에서 위치를 반전 시키면 결과가 완전히 변경됩니다. -> 선형 프리 페치?


의견은 긴 토론을위한 것이 아닙니다. 이 대화는 채팅 으로 이동 되었습니다 .
Samuel Liew

답변:


4

플래시에서의 코드 실행 속도는 각 분기 대상의 대기주기 수와 코드 정렬에 따라 다릅니다. STM32F103과 같은이 프로세서 및 이와 유사한 프로세서에서 코어가 가장 높은 주파수에서 실행될 때 플래시에는 3 개의 대기주기가 필요합니다. 즉, 각 분기는 2 ~ 5주기가 걸릴 수 있으며 이는 총 실행 시간에 영향을 줄 수 있습니다.

FLASH 속도 저하를 보완하기 위해이 프로세서에는 넓은 FLASH 버스와 페치 버퍼가 있습니다. SAM3X에는 한 쌍의 128 비트 명령어 버퍼가 있으며 프리 페치 패턴으로 채워져있는 것 같습니다 [1].

타이트한 루프를 최적화하려면 32 바이트 코드 블록에 맞추고 16 바이트 경계 (또는 경우에 따라 더 나은 32)로 정렬하십시오. 또한이 MCU에서 FLASH 매개 변수가 올바르게 설정되어 있는지 (예 : 프리 페치가 활성화되고 버스 폭이 128 비트로 설정되어 있는지) 확인하는 것이 좋습니다. 코드를 RAM에 복사하는 것은 선택 사항 일 수 있지만 제대로 작동하는 페치 버퍼에 비해 고통스럽고 실제로 속도가 느려질 수 있습니다.

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.