1

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.
erip
  • 16,374
  • 11
  • 66
  • 121
Maciej Miklas
  • 3,305
  • 4
  • 27
  • 52
  • I've edited post - now it should be correct. – Maciej Miklas Jan 04 '16 at 18:16
  • You're not passing it by reference, you're passing a pointer. – erip Jan 04 '16 at 18:18
  • There are a lot of problems with this code. – erip Jan 04 '16 at 18:25
  • I'm new in c++ - what is wrong with it? – Maciej Miklas Jan 04 '16 at 18:27
  • 1
    There's a bit of a semantic issue with this question: C++ has actual reference types, where a *reference* would be indicated like `KitData&`. You're passing a *pointer*, i.e. `KitData*`, not a reference. C *always* passes parameters by value, so C programmers pass pointers to pass by "reference" in effect, but calling that a reference gets confusing when you're talking about C++. – Caleb Jan 04 '16 at 18:54

2 Answers2

5

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.

JSF
  • 5,281
  • 1
  • 13
  • 20
  • 1
    This is just a pseudocode - real code does much more. – Maciej Miklas Jan 04 '16 at 19:12
  • @MaciejMiklas Because `kd` is a copy, the compiler doesn't do anything to it. It isn't returned or referenced. It's local, so changes aren't reflected outside this scope. The compiler can jump over all of this code. – erip Jan 04 '16 at 19:14
  • 3
    If your real code isn't recognized by the optimizer as doing nothing, it may still be recognized by the optimizer as doing **LESS** than the pass by pointer version does. The compiler knows any change you make in a pass by value object will be discarded on return. – JSF Jan 04 '16 at 19:15
3

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.

Community
  • 1
  • 1
erip
  • 16,374
  • 11
  • 66
  • 121
  • 1
    OP found pass by value to be 30% faster than pass by reference. You present a fine explanation of the difference between the two, but you haven't explained the OP's result. – Caleb Jan 04 '16 at 18:42
  • 1
    So your answer is basically "optimizations," without any indication of what kind of optimization you might be talking about. Sounds like you're just guessing rather than providing an answer based in fact. – Caleb Jan 04 '16 at 18:44
  • Without an MVCE, it's all speculation. I've included a link to optimizations that the compiler *can* make. Unfortunately I can't compile `KitData *kd = ....`, so I can't make any "answers based in fact." – erip Jan 04 '16 at 18:47
  • 2
    There is no reason to jump to your "incorrect algorithm" conclusion. It is perfectly plausible that those parts of the object modified by the function have no purpose after the function exits: Those parts might be consistently re-modified before next use after any exit of the function. That is a good fit for the kind of situation in which inlining a pass-by-value function would reveal extra optimization possibilities. – JSF Jan 04 '16 at 19:24
  • @JSF Either `paintOnKitVal` or `paintOnKitRef` is incorrect - they do different things where OP believes they do the same thing. – erip Jan 04 '16 at 19:26
  • 1
    This is interesting: http://playground.arduino.cc/Code/Pointer - "Passing by reference & or * will increase performance when using datatypes that are larger than 8bit." - my structure has at least 16 bytes. So ... either is my test wrong, or there is an exception when passing structs. – Maciej Miklas Jan 04 '16 at 19:42
  • this is only a pseudocode - in real test methods are containing real logic – Maciej Miklas Jan 04 '16 at 20:07
  • `paintOnKitVal` and `paintOnKitRef` do the same thing for sure - in my test I've just copied original method: `paintOnKit` and changed way of accessing `kd`. – Maciej Miklas Jan 04 '16 at 20:41
  • 1
    @MaciejMiklas The issue is that you call `paintOnKitRef` 5000 times in a loop with the same `kd`, and that `kd` is **modified** each time you call the method. Because you're passing a pointer to `kd` rather than a copy, those changes persist. To illustrate: try stepping through two calls to `paintOnKitVal` and then do the same for `paintOnKitRef`. In the former case, both calls will have the same values in `kd`; in the latter case, the values in `kd` will be different the second time around. – Caleb Jan 04 '16 at 21:33