Maciej Miklas
Maciej Miklas

Reputation: 3331

Arduino - passing struct by pointer seems to be slower than by value

I've written some pseudocode that should explain problem that I've discovered in my real application (Arduino 1.6 - https://github.com/maciejmiklas/LEDDisplay):

Display.h:

class Display {

public:
    void testRef();
    void testVal();

private:
    typedef struct {
            uint8_t xOnFirstKit;
            uint8_t yOnFirstKit;
            uint8_t xRelKit;
            uint8_t yRelKit;
            uint8_t xRelKitSize;
            uint8_t yRelKitSize;
            uint8_t xDataBytes;
            uint8_t xKit;
            uint8_t yKit;
            uint8_t xOnKit;
            uint8_t yOnKit;
            uint8_t xOnKitSize;
            uint8_t yOnKitSize;
            uint8_t xOnScreenIdx;
            uint8_t yOnScreenIdx;
            uint8_t yDataIdx;
        } KitData;

 inline void paintOnKitRef(KitData *kd); 
 inline void paintOnKitVal(KitData kd); 
}


Display.cpp:

#include "Display.h"

void Display::testRef(){
    KitData *kd = ....

    for(int i = 0 ; i < 5000 ; i++){
       paintOnKitRef(kd);
       ....
    }
}

void Display::testVal(){
    KitData *kd = ....

    for(int i = 0 ; i < 5000 ; i++){
       paintOnKitVal(*kd);
       ....
    }
}

inline void Display::paintOnKitRef(KitData *kd){
    for(int i = 0 ; i < 100 ; i++){
        kd->yDataIdx++;
        kd->yOnScreenIdx++;
        .....
    }
}

inline void Display::paintOnKitVal(KitData kd){
    for(int i = 0 ; i < 100 ; i++){
        kd.yDataIdx++;
        kd.yOnScreenIdx++;
        .....
    }
}

I have structure: KitData which is larger than 16 bytes, so I've decided to pass it by pointer instead of by value - it works as expected.

I've measured execution times and it looks like passing by value (testVal()) is about 30% faster than passing by reference (testRef()).

Is this normal?

Edit:

code above is only a pseudocode - in my real test methods: paintOnKitVal() and paintOnKitRef() are containing real code executing many operations and other methods. Both methods also do the same thing - only difference is way of accessing kd (trough pointer or dot notation).

This is the real test class: https://github.com/maciejmiklas/LEDDisplay/blob/callByPoint/Display.cpp

  1. Execute test method: paint(...) - this will use call-by-pointer as you can see in line 211
  2. Comment out line 211 and remove comment from line 212 - from now test will use call-by-value and execution time will be shorter.

Upvotes: 1

Views: 749

Answers (2)

JSF
JSF

Reputation: 5321

This part of your code does absolutely nothing and the optimizer recognizes that:

inline void Display::paintOnKitVal(KitData kd){
    for(int i = 0 ; i < 100 ; i++){
        kd.yDataIdx++;
        kd.yOnScreenIdx++;
    }
}

You imagine you tested the performance of pass by value. But you really tested the ability of the compiler to recognize the fact that the code does nothing.

When you pass by pointer (what a C programmers might call "by reference" but a C++ programmer would not "by reference) the function alone cannot be said to do nothing. An optimizer would need to take a wider understanding of the whole program to detect lack of effect.

Upvotes: 5

erip
erip

Reputation: 16935

Difference between pass-by-value and pass-by-reference:

Pass-by-value:

void foo(int a) {
  a = 30; // passed in param is now 30 until end of scope
}

int main() {
  int b = 3;
  foo(b); // copy of b is made, copy is assigned value 30
  // b is still 3
}

Pass-by-reference:

void foo(int& a) {
  a = 30; // passed in param is now 30 because a reference was passed in
}

int main() {
  int b = 3;
  foo(b); // reference to b is assigned value 30
  // b is now 30
}

Passing a pointer is similar to pass-by-reference, with some differences outlined here.

The code you've written for testVal will perform manipulations on a copy of kd. This is not what you want.

For small structs, the speed of pass-by-value and pass-by-reference will be similar. However, the memory footprint will be very different. Passing-by-value will make copies each time something is passed, which will take a lot of memory.

As for why it's faster:

There are likely optimizations because copies are being made instead of actual changes to the passed-in object that the compiler is making for you. This is done, however, at the cost of an incorrect algorithm.

After passing-by-values, the changes will not be reflected in kd that is passed in. The pointers' changes will be reflected and will be correct.

Upvotes: 3

Related Questions