1
\$\begingroup\$

I'm trying to determine the number of clock cycles it takes to run some code I've written for an STM32, because I'm curious to see just how much slower it is if I use floating point numbers. This is using an STM32F103C8T6 "blue pill" with the STM32duino core.

I'm using the built-in debug cycle counter to do this, but the results I'm getting seem incorrect. It is reporting that it takes 3 cycles to divide an integer by a float (Ex. 5/3.14159) and 4 cycles to do the same thing and cube the result (Ex. (5/3.14159)^3).

I highly suspect these numbers are far too low, but what could be causing these incorrect results?

int i = 1;

void setup() {
  Serial.begin(9600);
  DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk  ; // enable the counter
  CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
  DWT->CYCCNT = 0; // reset the counter
}

float divideByPi (int input){
  return input/3.14159;
}

void loop() {
  __disable_irq();
  uint32_t t1 = DWT->CYCCNT;
  float val = divideByPi(i);
  //val = val*val*val;
  uint32_t t2 = DWT->CYCCNT;
  __enable_irq();
  uint32_t diff = t2 - t1;
  

  Serial.print(i);
  Serial.print("/3.14159=");
  // Serial.print("(");
  // Serial.print(i);
  // Serial.print("/3.14159)^3=");
  Serial.println(val,6);

  Serial.print("t1=");
  Serial.print(t1);
  Serial.print("\tt2=");
  Serial.print(t2);
  Serial.print("\tdiff=");
  Serial.println(diff);
  Serial.println();
  delay(500);

  i++;
}
5/3.14159=1.591551
t1=143998082    t2=143998085    diff=3

6/3.14159=1.909861
t1=179998337    t2=179998340    diff=3

7/3.14159=2.228171
t1=215998082    t2=215998085    diff=3
(5/3.14159)^3=4.031453
t1=143998229    t2=143998233    diff=4

(6/3.14159)^3=6.966350
t1=179998228    t2=179998232    diff=4

(7/3.14159)^3=11.062304
t1=215998228    t2=215998232    diff=4

If I add another float val2 = divideByPi(i+1); in the benchmarked section and another line to print it out, suddenly the result goes to 1813 cycles.

5/3.14159=1.591551
1.909861
t1=143997166    t2=143998979    diff=1813

6/3.14159=1.909861
2.228171
t1=179997152    t2=179998965    diff=1813

7/3.14159=2.228171
2.546481
t1=215997182    t2=215998995    diff=1813

Edit: At the suggestion of Tom L, I looked at the disassembly and also tried it with a lower compiler optimization level. I was previously compiling with the -Os level.

Default -Os optimization level, -g debug symbols enabled

08002b50 <loop>:

float divideByPi (int input){
  return input/3.14159;
}

void loop() {
 8002b50:   e92d 43f7   stmdb   sp!, {r0, r1, r2, r4, r5, r6, r7, r8, r9, lr}
  \details Disables IRQ interrupts by setting the I-bit in the CPSR.
           Can only be executed in Privileged modes.
 */
__STATIC_FORCEINLINE void __disable_irq(void)
{
  __ASM volatile ("cpsid i" : : : "memory");
 8002b54:   b672        cpsid   i
  __disable_irq();
  uint32_t t1 = DWT->CYCCNT;
  float val = divideByPi(i);
 8002b56:   4d2a        ldr r5, [pc, #168]  ; (8002c00 <loop+0xb0>)
  uint32_t t1 = DWT->CYCCNT;
 8002b58:   4c2a        ldr r4, [pc, #168]  ; (8002c04 <loop+0xb4>)
  return input/3.14159;
 8002b5a:   6828        ldr r0, [r5, #0]
 8002b5c:   f7ff fc32   bl  80023c4 <__aeabi_i2d>
 8002b60:   a325        add r3, pc, #148    ; (adr r3, 8002bf8 <loop+0xa8>)
 8002b62:   e9d3 2300   ldrd    r2, r3, [r3]
 8002b66:   f7ff fdc1   bl  80026ec <__aeabi_ddiv>
 8002b6a:   f7ff ff65   bl  8002a38 <__aeabi_d2f>
  uint32_t t1 = DWT->CYCCNT;
 8002b6e:   f8d4 8004   ldr.w   r8, [r4, #4]
  return input/3.14159;
 8002b72:   4606        mov r6, r0
  //val = val*val*val;
  uint32_t t2 = DWT->CYCCNT;
 8002b74:   6867        ldr r7, [r4, #4]
  __ASM volatile ("cpsie i" : : : "memory");
 8002b76:   b662        cpsie   i
  __enable_irq();
  uint32_t diff = t2 - t1;
  

  Serial.print(i);
 8002b78:   4c23        ldr r4, [pc, #140]  ; (8002c08 <loop+0xb8>)
 8002b7a:   220a        movs    r2, #10
 8002b7c:   6829        ldr r1, [r5, #0]
 8002b7e:   4620        mov r0, r4
 8002b80:   f003 fc9a   bl  80064b8 <Print::print(int, int)>
...

-Og debug optimization level, -g debug symbols enabled

08002b50 <divideByPi(int)>:

float divideByPi (int input){
 8002b50:   b508        push    {r3, lr}
  return input/3.14159;
 8002b52:   f7ff fc37   bl  80023c4 <__aeabi_i2d>
 8002b56:   a304        add r3, pc, #16 ; (adr r3, 8002b68 <divideByPi(int)+0x18>)
 8002b58:   e9d3 2300   ldrd    r2, r3, [r3]
 8002b5c:   f7ff fdc6   bl  80026ec <__aeabi_ddiv>
 8002b60:   f7ff ff6a   bl  8002a38 <__aeabi_d2f>
}
 8002b64:   bd08        pop {r3, pc}
 8002b66:   bf00        nop
 8002b68:   f01b866e    .word   0xf01b866e
 8002b6c:   400921f9    .word   0x400921f9

08002b70 <loop>:

void loop() {
 8002b70:   e92d 43f0   stmdb   sp!, {r4, r5, r6, r7, r8, r9, lr}
 8002b74:   b083        sub sp, #12
  \details Disables IRQ interrupts by setting the I-bit in the CPSR.
           Can only be executed in Privileged modes.
 */
__STATIC_FORCEINLINE void __disable_irq(void)
{
  __ASM volatile ("cpsid i" : : : "memory");
 8002b76:   b672        cpsid   i
  __disable_irq();
  uint32_t t1 = DWT->CYCCNT;
 8002b78:   4c23        ldr r4, [pc, #140]  ; (8002c08 <loop+0x98>)
 8002b7a:   f8d4 8004   ldr.w   r8, [r4, #4]
  float val = divideByPi(i);
 8002b7e:   4d23        ldr r5, [pc, #140]  ; (8002c0c <loop+0x9c>)
 8002b80:   6828        ldr r0, [r5, #0]
 8002b82:   f7ff ffe5   bl  8002b50 <divideByPi(int)>
 8002b86:   4606        mov r6, r0
  //val = val*val*val;
  uint32_t t2 = DWT->CYCCNT;
 8002b88:   6867        ldr r7, [r4, #4]
  __ASM volatile ("cpsie i" : : : "memory");
 8002b8a:   b662        cpsie   i
  __enable_irq();
  uint32_t diff = t2 - t1;
 8002b8c:   eba7 0908   sub.w   r9, r7, r8
  

  Serial.print(i);
 8002b90:   4c1f        ldr r4, [pc, #124]  ; (8002c10 <loop+0xa0>)
 8002b92:   220a        movs    r2, #10
 8002b94:   6829        ldr r1, [r5, #0]
 8002b96:   4620        mov r0, r4
 8002b98:   f004 fac6   bl  8007128 <Print::print(int, int)>
...

The results are also much more sane with the debug optimization level. Calling divideByPi() once reportedly takes 926 cycles. Calling it once and cubing the result takes 1062 cycles. Calling the function twice takes 1840 cycles.

Getting back to my initial curiosity, floating point is, in fact, much slower.

926 cycles do it in floating point. Or 41 cycles to do it in fixed point (?).

uint32_t divideByPi (int input){
  return (input*100000000)/314159;  //returns equivalent to input/3.14159 to three decimal places, * 1000
}

void loop() {
  __disable_irq();
  uint32_t t1 = DWT->CYCCNT;
  uint32_t val = divideByPi(i);
  int high =  val / 1000;
  int low = val - (high*1000);
  uint32_t t2 = DWT->CYCCNT;
  __enable_irq();
  uint32_t diff = t2 - t1;
  

  Serial.print(i);
  Serial.print("/3.14159=");
  Serial.println(val);
  Serial.print(high);
  Serial.print(".");
  Serial.println(low);
...
\$\endgroup\$
2
  • \$\begingroup\$ Any chance the compiler is already doing some magic here? Have you checked the disassembly? What are your options regarding optimization? \$\endgroup\$
    – Tom L.
    Jul 6, 2023 at 20:52
  • \$\begingroup\$ I think you're right. I added the disassembly to the post at different optimization levels. Changing from -Os to -Og optimization levels seems to fix the issue. \$\endgroup\$
    – tinfever
    Jul 8, 2023 at 1:17

1 Answer 1

1
\$\begingroup\$

At the suggestion of Tom L, I looked at the disassembly and also tried it with a lower compiler optimization level. I was previously compiling with the -Os (smallest) level. Changing to the -Og (debug) level seems to have fixed the issue. I added more results to the original question. I don't know enough assembly to say precisely what the compiler is doing though.

\$\endgroup\$
1
  • \$\begingroup\$ If the numbers are all known at compile time the compiler may replace the calculation with the result. Furthermore if the result isn’t used in any capacity the compiler could remove the whole operation and result from the program. \$\endgroup\$
    – Bryan
    Jul 8, 2023 at 5:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.