Copied machine code of function slower than original (DUE)

Hi there,

so I've been experimenting a bit with machine code, execution from RAM and from flash. I'm using the Arduino Due and the standard Arduino Toolchain.

I have a function calc(), which has a body written in ARM Assembly and calculates a number (calculation on Due takes >1s). Now I tried to execute the raw machine code from RAM, once by defining an array and once by copying that array to a malloc'd region of RAM. Both work fine, yet execution from the malloc'd region is slower than from the array.

Now if I write the machine code to a different block in flash memory and try to execute it from there, it also executes fine, but far slower than when it's executed directly via a regular function call. (About the same speed as the malloc'd region)

#include <DueFlashStorage.h>
DueFlashStorage dueFlashStorage;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);

  unsigned char *rammem = (unsigned char *)malloc(1000);
  unsigned char raw[] = {0x21, 0x21, 0x32, 0xB5, 0x0, 0xF0, 0x3, 0xF8, 0xBD, 0xE8, 0x32, 0x40, 0x70, 0x47, 0x1, 0x25, 0x1, 0x39, 0x2, 0x29, 0xC0, 0xF0, 0xB, 0x80, 0x1, 0x25, 0x22, 0xB5, 0xFF, 0xF7, 0xF7, 0xFF, 0xBD, 0xE8, 0x22, 0x40, 0x2D, 0x18, 0x2, 0x39, 0x1, 0x29, 0x3F, 0xF6, 0xF6, 0xAF, 0x28, 0x0, 0x70, 0x47, 0x70, 0x47};

  for(int i=0; i<200; i++) {
    *(rammem+i) = raw[i];
  }

  dueFlashStorage.write(0,(unsigned char *)&raw,200);

  timeFnc(calc);
  timeFnc((int (*)(void))(IFLASH1_ADDR+1));
  timeFnc((int (*)(void))(rammem+1));
  timeFnc((int (*)(void))(raw+1));
}

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"
      ::
   );
}

void loop() {
  
}

The output of the program:

Address: 524617 Res: 3524578: 1354652us
Address: 786433 Res: 3524578: 1954441us
Address: 537334761 Res: 3524578: 1993470us
Address: 537427877 Res: 3524578: 1657181us

As we see, the calculated result is the same for all variants, the addresses confirm that two of the functions reside in RAM and two reside in flash memory. The first line is regular execution by calling the function, second line calls the function pointer pointing to the manually written flash block, third line function pointer to the malloc'd region, fourth line executing from the defined plain array.

The disassembly from arm-none-eabi-objdump for the calc function confirms that the machine code does not differ:

00080148 <_Z4calcv>:
      "bhi .lbla \n\t"
      ".lblb: \n\t"
      "movs r0,r5 \n\t"
      "bx lr \n\t"
      ::
   );
   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

I'm at the end of my wits. What could be going on here?

I have never tried to program in ARM assembler but the only thing I have experienced is that a code running from DUE flash runs faster than the same code running from SRAM. I guess that the code is highly optimized by the compiler to run from Flash or the Sam3x architecture is optimized for running code from Flash.

If I want to run a function from SRAM rather than Flash, I will be using:

attribute ((section (".ramfunc")))
before the function description.

This attribute is compulsory to read the DUE Unique Identifier, and IMO useful to reduce wear in Flash whenever you are in a debugging process uploading many many times a sketch until it runs properly....

Except if you really feel comfortable with ARM assembler, and for some rare functions, writing code in ARM assembler do not give any advantage.

Yes, I am aware of that. Thank you for the comment!

The thing I'm experiencing though, is that two different regions in flash (!) have different runtimes. Same for RAM: Two regions residing in ram exhibit different runtimes.

The discrepancy between execution from flash and RAM makes sense for me, a difference when executing from two different regions of flash not so much.

Check this out, an exact copy of the function right next to it (both in flash) runs 50% slower.

#include <DueFlashStorage.h>
DueFlashStorage dueFlashStorage;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);

  timeFnc(test2);
  timeFnc(calc);
}

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"
      ::
   );
}

int test2() {
  asm volatile(
    //".word 0x0023F04F \n\t"
    //".word 0x00004770 \n\t"


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

Address: 524669 Res: 3524578: 2058607us
Address: 524617 Res: 3524578: 1347557us

Disassembly to show it's exactly the same machine code: (Note the "different" endianness)

00080148 <_Z4calcv>:
      "bhi .lbla \n\t"
      ".lblb: \n\t"
      "movs r0,r5 \n\t"
      "bx lr \n\t"
      ::
   );
   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 <_Z5test2v>:
   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
    ".word  0x3902182D \n\t"
    ".word  0xF63F2901 \n\t"
    ".word  0x0028AFF6 \n\t"
    ".word  0x47704770 \n\t"
  );
}
   801b0:	4770      	bx	lr
	...