Aus dll geladene Funktionen genauso schnell wie "normale" Funktionen?



  • Hallo zusammen,

    ich hab mal ne Frage zu Funktionen die aus dlls importiert werden. Ich mache das mittels GetProcAddress und hab mich gefragt ob sich diese importierten Funktionen eigentlich genau gleich wie "normale" Funktionen verhalten?

    Also ist:

    extern "C" __declspec(dllexport) int myExternFoo()
    {
    	return getRandomInt();
    }
    

    nachdem sie importiert wurde vom Verhalten her genau das Gleiche wie

    int myNormalFoo()
    {
    	return getRandomInt();
    }
    

    welche direkt in dem Prozess meiner Anwendung läuft (Die Funktion getRandomInt() steht hier nur Beispielhaft)? Also vor allem Performance-mäßig würde mich interessieren ob es da Unterschiede gibt.

    Viele Grüße,
    hs



  • Deine Code-Beispiele passen nicht ganz zu deiner Frage. 😕

    Der Aufruf kann natürlich nicht geinlined werden. Außerdem liegt die Funktion in anderen Abschnitten im Arbeitsspeicher, die nicht unbedingt gecached sind. Dennoch ist der mögliche Overhead minimal bis praktisch nichts, solange du nicht irgendeinen komischen Kram machst, wie eine Funktion, die nur addiert.



  • Hi,

    wenn Du die Frage vor mehr als 20 Jahren gestellt hättest, würde ich antworten, daß der Aufruf mit einer dynamischen Bindung (also via GetProcAddress) am langsamsten ist, weil ja schließlich der Funktionszeiger noch aus dem Speicher geladen werden muß.

    Heutzutage läßt sich das aber sowas wegen Prefetching, CPU-Caches etc. nicht mehr so vorhersagen. Ich glaube auch, Intel schreibt auch nicht mehr in seiner Doku, wieviele Taktzyklen die Ausführung eines Befehls dauert, da die Info wertlos ist. Wenn überhaupt bewegen wir uns im Nanosekundenbereich.

    => Vergiß die Frage.

    mfg Martin



  • Marthog schrieb:

    Dennoch ist der mögliche Overhead minimal bis praktisch nichts, solange du nicht irgendeinen komischen Kram machst, wie eine Funktion, die nur addiert.

    mgaeckler schrieb:

    Wenn überhaupt bewegen wir uns im Nanosekundenbereich.

    => Vergiß die Frage.

    mfg Martin

    Sehr gut, genau das hatte ich gehofft 🙂

    Danke für die Antworten (aber warum ist eine Funktion die nur addiert komisch in diesem Kontext?^^)



  • Wenn die Funktion über dllimport wieder eingebunden wird, ist der Aufruf ein indirekter Sprung über eine Tabelle. Wenn die Funktion statisch bekannt wäre, wär das ein direkter Sprung. Wenn du das von Hand mit GetProcAddress machst, dürfte das auch noch ein direkter Sprung sein.
    Wollte ich nur mal ergänzen.



  • happystudent schrieb:

    Danke für die Antworten (aber warum ist eine Funktion die nur addiert komisch in diesem Kontext?^^)

    Nun, ein Funktionsaufruf erzeugt immer einen gewissen Oberhead. Der sollte natürlich in einem gesunden Verhältnis zur Laufzeit der Funktion stehen. Wenn sie aber nur zwei Zahlen addiert und die Summe zurückliefert, ist das definitiv nicht mehr gegeben. Dafür gibt es schließlich inline .

    mfg Martin



  • Bei MSVC sieht es folgendermassen aus:

    Bei nem "normalen" Funktionsaufruf hast du, sofern er nicht inline erweitert wird, einen direkten call als einzigen Sprungbefehl (+ 1x ret ).

    Bei nem Aufruf einer DLL Funktion hast du einen indirekten call + einen indirekten jmp (+ 1x ret )

    Der call führt dabei über den Import-Table in die DLL, der jmp steht bereits innerhalb der DLL und springt über den Relocation-Table der DLL die eigentliche Funktion an. Die beiden Tables werden verwendet um Speicher zu sparen, falls die DLL "rebased" werden muss. Durch die Verwendung von Tables werden alle Adressen die beim "rebasen" verändert werden müssen "zusammengepackt". Dadurch müssen dann wesentlich weniger Pages verändert werden als wenn man auf die Tables verzichten, und direkte Sprünge verwenden würde. (Und Pages die verändert werden müssen für jeden Prozess kopiert werden => verbraucht RAM und dauert länger beim Laden => schlecht.)

    D.h. der DLL Aufruf ist um "2x indirekt + 1x jmp " teurer. Wieviel auch immer das ist 😉

    Wieviel davon durch das PE-Format "aufgezwungen" wird, und wieviel davon "freiwillige" Optimierungen des Compilers in Richtung "weniger Pages kopieren" sind, weiss ich leider nicht auswendig. Falls es jmd. weiss möge er/sie es ergänzen.

    ps: 32 oder 64 Bit Code macht hier keinen Unterschied. Wobei mich das sogar etwas wundert, ich hatte in Erinnerung dass 64 Bit Code (AMD64) IP-relative Adressierung verwendet. Und damit wäre der Table innerhalb der DLL eigentlich nicht mehr nötig.
    Aber vielleicht tanzt Windows/Microsoft hier wiedermal aus der Reihe.


Log in to reply