Stack vs. Heap

Fazit

Pass by Value ist bei kleinen Objekten zu bevorzugen. Es kann besser optimiert werden, da es für den Compiler transparent ist.

Bei größeren Objekten ist ein gewisser overhead zur Vorbereitung des Funktionsaufrufs verbunden. Allerdings kann der Compiler mathematische Operation ggf. vorberechnen und loops unrollen. Dies gilt solange das Objekt auf dem Stack liegt, falls nicht muss es dorthin kopiert werden, das ist dann deutlich aufwendiger!
Es müssen also die Kosten für copy auf den stack vs. Pointer indirection (und damit einhergehende Intransparenz) abgewogen werden.
https://godbolt.org/z/7jPYhn9TP

CPUs laden Daten in Chunks (Cache line). Bei modernen Desktop-CPUs ist diese 64 Byte groß - bei vielen embedded Systemen (ESP32, ARM Cortex >M4) 32 Bytes. Maximalgröße für Pass-by-value sollte kleiner als eine Cache Line sein. Wenn das Object kleiner als 1-2 Words (also 8-16 Byte bei 64-bit Systemen) ist, kann es sogar direkt in einen/mehreren Registern geladen werden (und muss auch kein pointer offset berechnet werden).
Gallery of Processor Cache Effects (igoro.com)
siehe auch: CPU#Cache

Auf 8bit/16bit-Systemen mit wenigen Registern immer pass-by-reference!
https://stackoverflow.com/a/30987540

Grundlagen

Beispiel

Ohne compiler Optimierung:

Point p = {.x=5,.y=3};
int len = length(p);
Point *p = malloc(sizeof(Point));
p->x = 5;
P->y = 3;
int len = length(p);

Mit zusätzlicher globaler count variable, um zu verhindern, dass compiler alles statisch berechnet und weg optimiert:
Fall1 mit übergebenem struct wird immer noch vom compiler weg optimiert und statisch berechnet, fall2 mit malloc und Referenz nicht!
Aber funktion mit Berechnung mit pointer wird zu Fall1 ohne pointer optimiert (aka laden der member Adressen in Register wird übersprungen). Funktion für Fall1 ohne pointer ist nun shift Operation (?).
-> optimierung von pointer fall laggt einen zyklus hinterher
https://godbolt.org/z/Efdhaj65o

Pass-by-reference

C hat kein pass-by-reference! Alles wird als value übergeben.
Pass-by-reference und pass-by-pointer in C++ produzieren denselben Assembler Code!
https://godbolt.org/z/zc3asWoWc

Weiteres Beispiel mit einem Parameter, welcher verändert werden soll (pointer) und einem zusätzlichen wert (einmal value, einmal pointer). Assembly ist sehr ähnlich, pass by pointer hat im Grunde nur einen mov und eine dereferenzierung mehr. Dies bleibt auch über unterschiedliche -Ox settings ähnlich. Bei -O wird immer nur der erste call zu addScaled weg optimiert.
https://godbolt.org/z/4PW31a66o

Praktische Betrachtung im Playdate collision code: Kein messbarer Unterschied zwischen pass-by-value und pass-by-reference für Vector2D Klasse. Auch bei 20 zusätzlichen floats nicht. Erst bei 200 sinkt die Performance von pass-by-value merklich (5-10%).