Reputation: 3331
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
paint(...)
- this will use call-by-pointer as you can see in line 211Upvotes: 1
Views: 749
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
Reputation: 16935
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.
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