SOLVED - Arbeiten mit Sprungadressen
-
CJens schrieb:
"Sprungadresse C Function" nicht wirklich etwas brauchbares finden.
Das ist plausibel, da es diesen Begriff auch nicht gibt.
Die Adresse einer definierten Funktion ist ebenso wie die einer Variablen immer fest, da gibt es nichts umzurechnen oder zu optimieren.
Optimieren kannst du, indem du die Anzahl der Funktionsaufrufe reduzierst, indem du also wie SeppJ schon sagte, nicht immer nur ein Element zur Berechnung übergibst sondern einen Block von Elementen. Eine solche fachliche Optimierung schlägt immer eine technische.
Das switch/case kannst du optimieren, es wird dir aber nichts nützen, da die Auflösung der konstanten "Sprungadresse" der Funktionen vom Compiler sowieso gemacht werden muss, ob nun mit oder ohne switch.Hier mal eine optisch optimierte Variante, obwohl ich nicht glaube, dass ein Compiler hier großartig anderen Code erzeugen würde:
int Berechne(double* Stack, int nStack, int Type){ int i=0; for(i=0;i<nStack;i++) (Type==0?Function_0:(Type==1?Function_1:Function_2))(Stack+i); }
-
Also, ich sehe da kein Optimierungspotential, denn die Funktion wird in einer äußeren Iteration, die von 0 bis 300 zählt aufgerufen. Dabei müssen aber die Werte der vorherigen Iteration bekannt sein. Ich kann also schlecht einen ganzen Block übergeben. Es handelt sich um ein Zeitschrittverfahren und da muss ich den Zeitstrahl einhalten.
Kann man denn dann mehrfach auf die Funktion springen? Denn ich möchte die Schleife dann irgendwann gerne noch mit OpenMP parallelisieren. Das heißt, wenn Type bei einem Aufruf konstant bleibt, werden alle Threads dann auf die Funktion springen.
Ansonsten werde ich das mal versuchen und schauen, obs nen signifikanten Speedup gibt.
-
CJens schrieb:
Also, ich sehe da kein Optimierungspotential, denn die Funktion wird in einer äußeren Iteration, die von 0 bis 300 zählt aufgerufen. Dabei müssen aber die Werte der vorherigen Iteration bekannt sein. Ich kann also schlecht einen ganzen Block übergeben.
Diese Beschreibung passt nicht zu deiner vorherigen Beschreibung.
Aus deiner vorherigen Beschreibung:
for(i=0;i<nStack;i++){ Function(Stack[i]); }
Wenn dieses for in der Funktion selbst stecken würde, dann wären nStack-1 unnötige Funktionsaufrufe gespart.
-
Ich sehe auch kein richtiges Optimierungspotential in
Berechne
, ich denke nicht, dass dieswitch
Anweisungen so viel kosten, dass die Vermeidung davon etwas bringen würde. Wenn du aber auf dieswitch
Anweisungen verzichten willst, dann könntest du sowas machenint Berechne(double* Stack, int nStack, void (*func)(double*) ){ int i=0; for(i=0;i<nStack;i++) func(Stack + i); }
Und die User von
Berechne
müssen dann anstattType
zu übergeben, die Berechnungsfunktion übergebenvoid Function_0(double* x); void Function_1(double* x); void Function_2(double* x); void foo() { double *stack1, *stack2; size_t len1, len2; .... Berechne(stack1, len1, Function_2); Berechne(stack2, len2, Function_0); }
// edit:
SeppJ's Kommentar macht Sinn, wieso ist die for-Schleife außerhalb vonFunction_*
und nicht innerhalb der Funktionen selbst?
-
Bei Performanceproblemen sollte man nicht denken, sondern messen, was genau am langsamsten ist.
Na klar sollte man nicht unnötig Dinge ineffizient programmieren. Und: Schaffen Compiler es zum Beispiel, das switch automatisch aus der Schleife zu ziehen? Vermutlich nicht im allgemeinen Fall, aber vielleicht bei genügend einfachen Berechne-Funktionen? Naja, jedenfalls allgemein: immer messen!
-
Ich habe mal ein Beispiel vorbereitet und gemessen:
#include <stdio.h> #include <time.h> //#define JUMP //#define MEASURE double Function_1(double x); double Function_2(double x); double Function_3(double x); int main() { int Type = 0, i = 0, nStack = 100000000; void (*f)(double) = NULL; #ifdef MEASURE clock_t prgstart, prgende; prgstart = clock(); #endif do{ printf("Please select function:"); scanf("%i", &Type); }while (Type > 2 || Type < 0); switch (Type) { case 0: f = Function_1; break; case 1: f = Function_2; break; default: f = Function_3; break; } #ifdef JUMP for (i = 0; i < nStack; i++) { f((double)i); } #else for (i = 0; i < nStack; i++) { switch (Type) { case 0: Function_1((double)i); break; case 1: Function_2((double)i); break; default: Function_3((double)i); break; } } #endif #ifdef MEASURE prgende = clock(); printf("Laufzeit %.2f Sekunden\n", (float)(prgende - prgstart) / CLOCKS_PER_SEC); #endif getchar(); return 0; } double Function_1(double x) {return (x * x) / 1.0;} double Function_2(double x) {return (x * x) / 2.0;} double Function_3(double x) {return (x * x) / 3.0;}
Und was soll ich sagen: Mit der switch-case-Anweisung in der Schleife ist der Code ungefähr um Faktor 5 schneller
Verstehen kann ich das nicht, aber ist zumindest mal eine Erkenntnis und ich kann die Idee getrost ad acta legen. Wäre aber interessant, wenn jemand eine Idee hat, wie das sein kann.
-
Wenn man es wie folgt macht, ist der Unterschied nicht so groß:
#include <stdio.h> #include <time.h> //#define JUMP //#define MEASURE void Function_1(double x); void Function_2(double x); void Function_3(double x); static void(*f)(double) = NULL; int main() { int Type = 0, i = 0, nStack = 100000000; #ifdef MEASURE clock_t prgstart, prgende; prgstart = clock(); #endif do{ printf("Please select function:"); scanf("%i", &Type); }while (Type > 2 || Type < 0); switch (Type) { case 0: f = Function_1; break; case 1: f = Function_2; break; default: f = Function_3; break; } #ifdef JUMP for (i = 0; i < nStack; i++) { f((double)i); } #else for (i = 0; i < nStack; i++) { switch (Type) { case 0: Function_1((double)i); break; case 1: Function_2((double)i); break; default: Function_3((double)i); break; } } #endif #ifdef MEASURE prgende = clock(); printf("Laufzeit %.2f Sekunden\n", (float)(prgende - prgstart) / CLOCKS_PER_SEC); #endif getchar(); return 0; } void Function_1(double x) {double fc = (x * x) / 1.0;} void Function_2(double x) {double fc = (x * x) / 2.0;} void Function_3(double x) {double fc = (x * x) / 3.0;}
Also, void statt double für die Funktionen hat eine sehr große Auswirkung auf die Geschwindigkeit.
-
Hast du etwa unoptimierte Programme verglichen? Da dein Programm überhaupt nichts tut, sollte eigentlich so gut wie alles wegoptimiert werden. Das ist jedenfalls was bei mir passiert, nachdem ich die zahlreichen Warnungen entferne. Was passiert, wenn ich das undefinierte Verhalten im Programm belasse, habe ich nicht geprüft, da uninteressant.
Übersetzte main (GCC 5.3.1, O3) mit JUMP eingeschaltet:
main: .LFB23: .cfi_startproc subq $24, %rsp .cfi_def_cfa_offset 32 movq %fs:40, %rax movq %rax, 8(%rsp) xorl %eax, %eax movl $0, 4(%rsp) .p2align 4,,10 .p2align 3 .L2: movl $.LC0, %esi movl $1, %edi xorl %eax, %eax call __printf_chk leaq 4(%rsp), %rsi xorl %eax, %eax movl $.LC1, %edi call __isoc99_scanf cmpl $2, 4(%rsp) ja .L2 movq stdin(%rip), %rdi call _IO_getc xorl %eax, %eax movq 8(%rsp), %rdx xorq %fs:40, %rdx jne .L7 addq $24, %rsp .cfi_remember_state .cfi_def_cfa_offset 8 ret .L7: .cfi_restore_state call __stack_chk_fail .cfi_endproc
Die andere Variante:
main: .LFB23: .cfi_startproc subq $24, %rsp .cfi_def_cfa_offset 32 movq %fs:40, %rax movq %rax, 8(%rsp) xorl %eax, %eax movl $0, 4(%rsp) .p2align 4,,10 .p2align 3 .L2: movl $.LC0, %esi movl $1, %edi xorl %eax, %eax call __printf_chk leaq 4(%rsp), %rsi xorl %eax, %eax movl $.LC1, %edi call __isoc99_scanf cmpl $2, 4(%rsp) ja .L2 movq stdin(%rip), %rdi call _IO_getc xorl %eax, %eax movq 8(%rsp), %rdx xorq %fs:40, %rdx jne .L7 addq $24, %rsp .cfi_remember_state .cfi_def_cfa_offset 8 ret .L7: .cfi_restore_state call __stack_chk_fail .cfi_endproc
Man sieht: Komplett identisch. Zudem wurde alles zwischen Zeile 23 und dem return (zurecht) wegoptimiert. Laufzeit des Programms ist entsprechend ca. 0.
Wenn du also irgendwelche Geschwindigkeitsunterschiede gemessen haben möchtest, dann muss entweder deine Messmethode Schrott sein oder du hast keine aggressiven Optimierungen aktiviert. In beiden Fällen sind die Ergebnisse vollkommen wertlos. Wenn du beim echten Problem genauso nachlässig vorgegangen bist, kann man vergessen, dir hier sinnvoll helfen zu können.
edit: Label L7 hinzu gefügt, damit man sieht, dass da auch nichts passiert. Wenn man den Stackprotector ganz abschaltet und das unsinnige getchar rauswirft, kommt man zu:
main: .LFB23: .cfi_startproc subq $24, %rsp .cfi_def_cfa_offset 32 movl $0, 12(%rsp) .p2align 4,,10 .p2align 3 .L2: movl $.LC0, %esi movl $1, %edi xorl %eax, %eax call __printf_chk leaq 12(%rsp), %rsi xorl %eax, %eax movl $.LC1, %edi call __isoc99_scanf cmpl $2, 12(%rsp) ja .L2 xorl %eax, %eax addq $24, %rsp .cfi_def_cfa_offset 8 ret .cfi_endproc
Also ein Programm, dass 100% äquivalent ist zu
#include <stdio.h> int main() { int i; do{ printf("Please select function:"); scanf("%i", &i); }while (i > 2 || i < 0); return 0; }
-
Hallo.
Nein, ich nutze od, also keine Optimierung. Habe den Code leicht abgeändert:
#include <stdio.h> #include <time.h> //#define JUMP //#define MEASURE void Function_1(double x, double* y); void Function_2(double x, double* y); void Function_3(double x, double* y); static void(*f)(double) = NULL; int main() { int Type = 0, i = 0, nStack = 100000000; double Sum = .0; #ifdef MEASURE clock_t prgstart, prgende; prgstart = clock(); #endif do{ printf("Please select function:"); scanf("%i", &Type); }while (Type > 2 || Type < 0); switch (Type) { case 0: f = Function_1; break; case 1: f = Function_2; break; default: f = Function_3; break; } #ifdef JUMP for (i = 0; i < nStack; i++) { f((double)i, &Sum); } #else for (i = 0; i < nStack; i++) { switch (Type) { case 0: Function_1((double)i, &Sum); break; case 1: Function_2((double)i, &Sum); break; default: Function_3((double)i, &Sum); break; } } #endif #ifdef MEASURE prgende = clock(); printf("Laufzeit %.2f Sekunden\n", (float)(prgende - prgstart) / CLOCKS_PER_SEC); #endif printf("Summe: %2.4e\n", Sum); getchar(); return 0; } void Function_1(double x, double* y) { double fc = (x * x) / 1.0; *y += fc; } void Function_2(double x, double* y) { double fc = (x * x) / 2.0; *y += fc; } void Function_3(double x, double* y) { double fc = (x * x) / 3.0; *y += fc; }
Jetzt tut das Programm auch etwas. Außerdem - wie kann er die switch-case Anweisung wegoptimieren, wenn der Wert von Type erst zur Laufzeit festgelegt wird? Das hatte ich ja extra deshalb so gemacht.
-
Niemanden interessiert, wie schnell unoptimierter Code läuft.