Zeit für Methodenaufruf



  • Ich hab mal mit dem Programm Zeiten für Methodenaufrufe gemessen.

    class Interface
    {
    public:
    	virtual void f(int i) = 0;
    };
    
    class Foo : public Interface
    {
    public:
    	Foo():s(0){}
    	virtual void f(int i)
    	{
    		if ( i == 1 )
    			s = clock();
    		else if ( i == 400000000 )
    			std::cout<< clock() - s<<"\n";
    	}
    private:
    	clock_t s;
    };
    
    int main(int argc, char**)
    {
    	Foo* foo = new Foo();
    
    	boost::function<void(int)> bf = boost::bind(&Foo::f, foo, _1);
    	for(int i = 0; i<400000001*argc; i++)
    	{
    		bf(i);
    	}
    
    	void(Foo::*mf)(int);
    	mf = &Foo::f;
    	for(int i = 0; i<400000001*argc; i++)
    	{
    		(foo->*mf)(i);
    	}
    
    	Foo sfoo;
    	for(int i = 0; i<400000001*argc; i++)
    	{
    		sfoo.f(i);
    	}
    
    	Interface* ifa = foo;
    	for(int i = 0; i<400000001*argc; i++)
    	{
    		ifa->f(i);
    	}
    
    	for(int i = 0; i<400000001*argc; i++)
    	{
    		foo->f(i);
    	}
    
    }
    

    4067 //boost function
    1811 //member pointer
    455 //Foo auf Stack
    1519 //Aufruf über Interface
    1660 //Direkter Aufruf

    Boost ist deutlich am langsamsten, aber dafür ist das Objekt schon dran gebunden.
    Aufruf mit Interface oder direkt ist praktisch gleich schnell, aber man muss immer das Objekt haben.
    Aufruf mit Memberfunction Pointer ist etwas langsamer und man braucht das Objekt.
    Foo auf dem Stack ist am schnellsten. Dürfte wohl das virtual weg optimiert sein. Keine Polymorphie möglich, daher uninteressant.

    Gibts noch eine andere und schnellere Möglichkeit, die was ähnliches wie boost function macht?



  • Wenn's schnell gehen soll, bieten sich Funtoren+Templates an. Und sonst sind virtuelle Funktionen bzw boost::function gar nicht so übel. Man kann eben nicht alles haben. Vielleicht findest Du ja auch ein etwas anderes Design, wobei virtuelle Funktionen (bzw boost::function) nicht so oft aufgerufen werden müssen, so dass der Overhead nicht mehr ins Gewicht fällt.



  • Warum braucht die boost-Variante denn länger?

    In der Klasse ist doch auch nur das Objekt und der member pointer enthalten oder
    sehe ich das falsch?

    Kann evtl. ohne boost etwas wegoptimiert werden? Wenn ja, was?



  • Was ist eigentlich ein allgemeiner function member pointer?
    Falls es eine virtuelle Funktion ist, nur ein Index in die vtbl, aber falls es eine statische Funktion ist ein Zeiger auf die Einsprungadresse. Und dazu noch ein bool, um anzuzeigen, welche Sorte es ist. Und beim Aufruf muß erst mit if auf den bool gestetet werden, welche Sorte es ist und dann entweder direkt gesprungen werden oder erst die Tabelle gelesen und dann gesprungen werden.
    Ich denke, hier ist der Ärger zu suchen. Oder wenigstens ein Einsprungpunkt, um den Ärger bis zum Ende genau auseinandernehmen zu können.
    Edit:
    Ach, da habe ich ja noch function member pointers auf virtuelle Basisklassen vergessen. Dann brauchts ja noch den offset zu Basisklasse. Am Ende ist sizeof() für einen Methodenzeiger 3*sizeof(funktionszeiger). Komisch. Die armen Compilerbauer.



  • volkard schrieb:

    Dann brauchts ja noch den offset zu Basisklasse.

    Steht dieser Offset nicht eh schon in der vtable? Immerhin ist er für alle
    Objekte einer Klasse gleich...



  • Ich glaube nicht, dass diese Messung wirklich wiedergibt wie sich die Aufrufe verteilen. Was ist z.B. mit dem ersten Aufruf? Da muss der Code evtl. Erst in den Cache geladen werden - oder auch nicht. Das macht schon einen Riesenunterschied.

    Besser wäre es, den Assemblercode zu vergleichen und die benötigten Zyklen zu zählen. Dann gibt es zumindest ein ungefähres Bild, auch wenn in einer realen Anwendung noch andere Effekte eine Rolle spielen können.



  • autocode schrieb:

    Warum braucht die boost-Variante denn länger?

    Naja, erstmal hast Du bei boost::function zwei "Weiterleitungen". Du rufst boost::function auf, boost::function ruft eine interne Funktion auf. Die interne Funktion ruft das auf, was Du willst. Dazu kommt jetzt noch, dass Du eine weitere Indirektion dabei hast, weil Deine Funktion f virtuell ist.

    Wenn es schnell gehen soll, nimmst Du eben Templates+Funktoren, falls möglich. Das ist dann noch besser bzgl Performance weil der Funktions-Code relativ leicht "ge-inline-t" werden kann.

    Dann gibt es noch Dinge wie "The Impossibly Fast C++ Delegates", welche speziell für Elementfunktionen gedacht sind und nicht wie boost::function auch mit anderen Funktoren klarkommt. Der Trick ist dabei Elementfunktionszeiger als Templateparameter zu benutzen.

    kk



  • Bist du sicher, dass du bei dem Test auch mit Optimierungen kompiliert hast?
    Wenn ich das laufen lasse, kommt nämlich folgendes heraus:

    `1593

    1195

    664

    1062

    1195`

    Womit der Unterschied zwischen der boost-Methode und der herkömmlichen eigentlich fast schon nicht mehr der Rede wert ist.


Anmelden zum Antworten