Methodenaufrufe sind über Interfaces langsamer.

Status
Nicht offen für weitere Antworten.

Marco13

Top Contributor
Eigentlich war mir die Tatsache, dass Methodenaufrufe über interfaces theoretisch etwas langsamer sind, schon bewußt ("Function Pointer Table", virtuelle Aufrufe und so...). Aber in einem bestimmten Fall ist das bei mir jetzt so deutlich zum Tragen gekommen, dass ich mal diesen Microbenchmark gebaut habe. Die sind natürlich immer mit Vorsicht zu genießen, aber es scheint (auch in dem anderen Programm) so dass bei sowas wie
Code:
interface TestInterface
{
    int get();
}

class TestClass implements TestInterface
{
    private int n = 123;
    public int get() { return n; }
}

ein Aufruf von

testInterface.method();

tatsächlich etwa 25% langsamer ist als ein Aufruf von

((TestClass)testInterface).method();

Hm. Blöd, irgendwie...

Code:
interface TestInterface
{
    int get();
}

class TestClass implements TestInterface
{
    private int n = 123;
    public int get() { return n; }
}

class InterfaceSpeedTest
{
    public static void main(String args[])
    {
        TestInterface data[] = createData(50000);
        for (int runs=1000; runs<=100000; runs*=10)
        {
            runInterface(data, runs);
            runClass(data, runs);
        }
    }

    private static TestInterface[] createData(int size)
    {
        TestInterface data[] = new TestInterface[size];
        for (int i=0; i<data.length; i++)
        {
            data[i] = new TestClass();
        }
        return data;
    }


    private static void runInterface(TestInterface data[], int runs)
    {
        int sum = 0;
        long before = System.currentTimeMillis();
        for (int i=0; i<runs; i++)
        {
            sum += runInterface(data);
        }
        long after = System.currentTimeMillis();
        System.out.println(sum+" "+runs+" interface "+(after-before));
    }

    private static int runInterface(TestInterface data[])
    {
        int sum = 0;
        for (int i=0; i<data.length; i++)
        {
            sum += data[i].get();
        }
        return sum;
    }



    private static void runClass(TestInterface data[], int runs)
    {
        int sum = 0;
        long before = System.currentTimeMillis();
        for (int i=0; i<runs; i++)
        {
            sum += runClass(data);
        }
        long after = System.currentTimeMillis();
        System.out.println(sum+" "+runs+" class     "+(after-before));
    }

    private static int runClass(TestInterface data[])
    {
        int sum = 0;
        for (int i=0; i<data.length; i++)
        {
            sum += ((TestClass)data[i]).get();
        }
        return sum;
    }


}
 
und das Zusammenrechnen dauert auch noch seine Zeit,
ohne dieses ist das Interface (relativ) noch langsamer, je nachdem ob man dem Test vertraut:
Code:
class InterfaceSpeedTest
{
    public static void main(String args[])
    {
        TestInterface data = new TestClass();
        int[] a = new int[]
            {1000, 5000, 10000, 20000};
        for (int i = 0; i < a.length; i++)
        {
            runInterface(data, a[i]);
            runClass(data, a[i]);
        }
    }


    private static void runInterface(TestInterface data, int runs)
    {
        long before = System.currentTimeMillis();
        for (int j = 0; j < 100000; j++)
        {
            for (int i = 0; i < runs; i++)
            {
                data.get();
            }
        }
        long after = System.currentTimeMillis();
        System.out.println(" " + runs + " interface " + (after - before));
    }

    private static void runClass(TestInterface data, int runs)
    {
        long before = System.currentTimeMillis();
        for (int j = 0; j < 100000; j++)
        {
            for (int i = 0; i < runs; i++)
            {
                ((TestClass)data).get();
            }
        }
        long after = System.currentTimeMillis();
        System.out.println(" " + runs + " class     " + (after - before));
    }


}

-----

Ausgabe:
 1000 interface 938
 1000 class     359
 5000 interface 4250
 5000 class     1984
 10000 interface 8531
 10000 class     3329
 20000 interface 17687
 20000 class     6672
 
bei mir ca gleich:
650000000 1000 interface 509
650000000 1000 class 662
6500000000 10000 interface 4944
6500000000 10000 class 4356
65000000000 100000 interface 21557
65000000000 100000 class 23700

run 2:
650000000 1000 interface 279
650000000 1000 class 265
6500000000 10000 interface 2447
6500000000 10000 class 2492
65000000000 100000 interface 21882
65000000000 100000 class 23797

PS: habe den int sum in einen long sum geändert
Ubuntu 8.10 java 1.6_10

EDIT:
ok test nach SlaterB:
Code:
 1000 interface 11
 1000 class     8
 5000 interface 8
 5000 class     5
 10000 interface 0
 10000 class     0
 20000 interface 1
 20000 class     0

//und weil so schön war grad run2:
 1000 interface 11
 1000 class     9
 5000 interface 8
 5000 class     5
 10000 interface 0
 10000 class     0
 20000 interface 0
 20000 class     1
 
Hm okay ich schreib nie wieder Interfaces für komplexe Algorithmen oder irgendwelche Simulationen :shock:

Meine Ergebnisse bestätigen auch die 25%-Regel:

1000 class 121
1000 interface 159
10000 class 1214
10000 interface 1562
100000 class 12008
100000 interface 15520
 
Ich vertrau dem Test nicht. Da ist ja der JIT-Compiler noch nichtmal fertig mit kompilieren. 🙂
 
Ja, solche Tests sind immer heikel, aber ... er ruft zig-Tausendmal die gleichen Methoden auf - wann sollte der JIT dann noch was machen?

@SlaterB: das Zusammenrechnen dauert auch noch seine Zeit,
Klar, aber wenn man das nicht macht, KÖNNTE er potentiell erkennen, dass in der Schleife nichts passiert, und sie komplett wegoptimieren. Z.B. wäre die Ausführungszeit von
for (int i=0; i<1000000; i++) { i<<=1; i/=2; }
bei den meisten C-Compilern wohl 0 Millisekunden...

OK. Es scheint, als würde die Theorie stimmen: Aufrüfe über Interfaces SIND langsamer (wie viel genau sei mal dahingestellt, aber weniger als 25% Verlangsamung traten bisher nicht auf). Für irgendwelche zeitkritischen, "feingranularen" Aufrufe würden sich Interfaces damit ja eigentlich disqualifizieren... 🙁
 
OK. Es scheint, als würde die Theorie stimmen: Aufrüfe über Interfaces SIND langsamer (wie viel genau sei mal dahingestellt, aber weniger als 25% Verlangsamung traten bisher nicht auf). Für irgendwelche zeitkritischen, "feingranularen" Aufrufe würden sich Interfaces damit ja eigentlich disqualifizieren... icon_sad.gif
Klar sind interfaces ein bisschen langsamer, fällt aber in der Praxis nicht auf.

25% Zeit von einem return 123 sind nicht wirklich viel bzw. lange, bei echten Methoden die auch mehr machen sollte das nicht ins Gewicht fallen.
 
> Für irgendwelche zeitkritischen, "feingranularen" Aufrufe würden sich Interfaces damit ja eigentlich disqualifizieren...

das muss man alles relativ sehen
falls ein Methodenaufruf alleine gegenüber direkten Code schon 10ns vs 0ns bedeutet, also über das 1-Billion-fache, dann schaden die 100% mehr durch das Interface auch nicht,

normalerweise hat man doch nicht massig Interface-Aufrufe, vom Abfragen gigantischer Datenmengen etwa in Listen abgesehen,
und selbst dann ist es schon sehr schwer, irgendeine Verarbeitung zu finden (wie das einfache int-Addieren in deinem Beispiel),
die so schnell ist, dass die Methodenaufrufe an der Gesamtzeit einen relevanten Anteil haben,
(edit: wenn man jede höhere Methode wie Parsen/ Formatieren/ Streams auch in Methodenanteil/ sonstiges dividiert,
dann siehst vielleicht schon bisschen anders aus, aber man kann ja nicht die halbe Java-API ausklammern)


ob eine Verarbeitung mit 1000 Codezeilen in einer Methode nun eine Zeit x dauert,
oder ein objektorientiert verständliches Programm ohne Interfaces nun 1% langsamer ist
oder ein noch schöner objektorientiert verständliches Programm mit Interfaces nun 2% langsamer ist,
das ist doch egal
 
Klar sind interfaces ein bisschen langsamer, fällt aber in der Praxis nicht auf.

Code:
public interface Searchable{

         public String[] getOptimalPath(); // rekursiv durch DFS
}

public class Tree implements Searchable(){
       ...
}

public class Test{

         public static void main(String[] args){
                 
                Searchable s = new Tree();
                s.setNodes(100000000000);
                s.getOptimalPath(); // Rechenzeit ~ 1 Stunde
                ((Tree)s).getOpimtalPath(); // Rechenzeit ~ 45 Minuten
         }
}

... man kann also pauschal nicht sagen, dass es egal ist. Das Bsp im Code ist übrigens eine Sache
die fast in jedem Programm, dass grössere Mengen an Daten verwaltet, vorkommt. Und das macht
auf jeden Fall einen Unterschied!

PS: Die Zeitangabe ~ 1 Stunde ist vollkommen geraten, ich kann das jetzt nicht abschätzen, könnte
auch nur 15 Mins dauern, vllt aber auch 1 Tag.
 
. man kann also pauschal nicht sagen, dass es egal ist.
Doch doch, ich behaupte immer noch dass es absolut irrelevant ist für die meisten Anwendungsfälle.

Übrigens, du gehst immer noch von 25% aus, dass war aber nur im Beispiel von Marco der Fall, wo keine Berechnung/Auswertung durchgeführt wurde, nur ein simples return, 25% von dieser Zeit sind immer noch nix (in realen Massstäben) 🙂

Alles in allem sollte man sich vor Micro-Optimierungen hüten und nur einen Profiler nutzen um die langsamen Stellen im Code zu finden, keine Faustregeln etc., die 80/20 Regel gilt da nämlich immer ganz besonders 😉
 
Du nennst 15 Minuten Mikro-Optimierung? Häng eine Null mehr dran und es werden 15 Stunden.

Ich bin generell auch deiner Meinung, dass Übersichtlichkeit und Fehlerfreiheit wichtiger sind als Geschwindigkeit,
weil die Rechner heutzutage eh schon jenseits von Gut und Böse sind.
Aber frag mal paar Leute aus dem Umfald Genforschung oder beim Falten von Proteinen, was die zu dir sagen
wenn du meinst dein neuer Algorithmus ist 0,0002% schneller. Die küssen dir die Füsse, weil ihr Super-Rechner
seit 4 einhalb Monaten zwei Wasserstoffmoleküle vergleicht.
 
Die 25% sind doch totaler quatsch. Testet es mit einer Operation, die mal wirklich Zeit kostet. Dann werdet Ihr feststellen, dass der Overhead fix ist und nicht abhängig von der Laufzeit der Methode.
 
>> Du nennst 15 Minuten Mikro-Optimierung? Häng eine Null mehr dran und es werden 15 Stunden.

Wie gesagt, du gehst immer noch von 25% aus und skalierst die dann einfach auf "echte" Methoden.. schon klar dass es dann nach "viel" aussieht.

Ist aber imho eine falsche Annahme die dich zu falschen Ergebnissen führt.
 
Mein Bsp könnte "echter" nicht sein. Ein Tiefendurchlauf verläuft rekursiv, wobei die Rechenzeit pro Durchlauf NIX ist,
aber sie wird hunderttausendfach rekursiv aufgerufen. Von daher kann ich auf jeden Fall von den 25% ausgehen.
 
Die 25% kommt dadurch zustande, dass ein return 100; in etwa so "lange" dauert wie der Overhead durch den virtuellen Function Call (nämlich quasi nix). Macht doch mal ein TimeUnit.SECONDS.sleep(1); vor dem return und testet dann nochmal. :roll:
 
und ein return boolean, wie es bei DFS der Fall ist, dauert genauso "lang" wie ein return 100.
 
hdi hat gesagt.:
Mein Bsp könnte "echter" nicht sein. Ein Tiefendurchlauf verläuft rekursiv, wobei die Rechenzeit pro Durchlauf NIX ist,
aber sie wird hunderttausendfach rekursiv aufgerufen. Von daher kann ich auf jeden Fall von den 25% ausgehen.
Nochmal: Eine Methode, die praktisch nichts macht, wird über Interface 25% langsamer aufgerufen, also z.B. in 4 statt 3 Mikrosekunden. Wenn sich deine Funktion 100000 mal rekursiv aufruft (wieso macht sie das eigentlich per Interface?), dann entspricht das einem Verlust von 100000 x 0,000001 = 0,1 Sekunden. Wo ist jetzt das Performance-Problem?
 
hdi hat gesagt.:
Du nennst 15 Minuten Mikro-Optimierung? Häng eine Null mehr dran und es werden 15 Stunden.

Muahahaha... Schon mal was vom Sexagesimalsystem gehört?

Was das Thema angeht: Ich hatte völlig unterschiedliche Angaben. Der JDT-Compiler hat im Gegensatz zum Sun-Compiler bei mir alles wegoptimiert. Den letzten Aufruf (20000 class) schaffe ich auch mit dem Sun-Compiler in 0 ms. Immer wieder. Bedeutet für mich: Der Test sagt nichts.

Zur Theorie beschäftigt mich folgende Frage: Aus welchem Grund sollte der Interface-Aufruf auch nur ein bisschen mehr Zeit beanspruchen? Das ergibt überhaupt keinen Sinn. In beiden Fällen sind die Methoden-Aufrufe virtuell, das heißt es hängt in beiden Fällen am Objekt genau eine Referenz zum Code-Block. Das darf genau keine Zeit fressen. Wer erklärt's mir?

Ebenius
 
Schon mal was vom Sexagesimalsystem gehört?

Das mit 15 Stunden war symbolisch gemeint, aber es ist ja wohl verständlich was ich meine oder?

Wenn sich deine Funktion 100000 mal rekursiv aufruft (wieso macht sie das eigentlich per Interface?), dann entspricht das einem Verlust von 100000 x 0,000001 = 0,1 Sekunden. Wo ist jetzt das Performance-Problem?

Wenn das so ist, warum bekommen wir dann beim Test Zeitgewinnung von 4 Sekunden?
Ob die Methode nix macht, oder eine Stunde rechnet, spielt doch ausserdem keine Rolle für die Zeitgewinnung.

PS: getOptimalPath() könnte die Interface-Metohde expand(queue,...) aufrufen, ist ja jetz egal es geht
ums Prinzip.
 
Wooohooo.. Vorsicht:

@SlaterB: das muss man alles relativ sehen... und
@byto: Die 25% sind doch totaler quatsch. Testet es mit einer Operation, die mal wirklich Zeit kostet.

Deswegen hatte ich explizit gesagt, dass dieses Problem nur bei "feingranularen Methoden auftritt. Natürlich fällt das ganze nicht ins Gewicht, wenn man über das Interface eine Methode aufruft, die eh 10 Minuten rechnet. Aber wenn diese Interface-Methode eben etwas SEHR einfaches macht, und SEHR oft aufgerufen wird, dann kann das ganze schon kritisch werden.

Im konkreten Fall ging es (grob gesagt) um eine "size()"-Methode, die je nach Implementierung z.B. entweder eine array.length oder eine list.size() zurückgeben kann (der genaue Kontext ist nicht so wichtig, nur als Beispiel zur Verdeutlichung - die Frage, ob man an der Klassen/Methodenstruktur etwas ändern könnte, um das Problem abzumildern, sei mal außen vor gelassen (bzw. beantwortet mit einem "So einfach ist das in diesem Fall nicht")). Und wenn eine Schleife, die grob eine Form hat wie
Code:
for (int i=0; i<interfaceObject.size(); i++) 
{
    sum += interfaceObject.getValue(i);
}
eben tatsächlich ein Kernelement des Algorithmus ist, und der z.B. 10 Sekunden braucht, und die gleiche Schleife in der Form
Code:
for (int i=0; i<((SomeClass)interfaceObject).size(); i++) 
{
    sum += ((SomeClass)interfaceObject).getValue(i);
}
nur 7.5 Sekunden braucht und damit ganze 25% schneller ist (aber natürlich unbrauchbar wegen der fehlenden Generizität), dann finde ich das schon ärgerlich...
 
Kann mir bitte trotzdem jemand erklären, wieso das einen Unterschied machen soll? Beide Aufrufe sind virtuell. Der Aufwand ist exakt der gleiche!
 
Marco13 hat gesagt.:
Deswegen hatte ich explizit gesagt, dass dieses Problem nur bei "feingranularen Methoden auftritt. Natürlich fällt das ganze nicht ins Gewicht, wenn man über das Interface eine Methode aufruft, die eh 10 Minuten rechnet. Aber wenn diese Interface-Methode eben etwas SEHR einfaches macht, und SEHR oft aufgerufen wird, dann kann das ganze schon kritisch werden.
Das wird dank Inlining und Inline Caching nicht kritisch werden. Eben letzteres macht den Overhead durch den v-table obsolet.
 
Wenn das so ist, warum bekommen wir dann beim Test Zeitgewinnung von 4 Sekunden?
Ob die Methode nix macht, oder eine Stunde rechnet, spielt doch ausserdem keine Rolle für die Zeitgewinnung.
Dann sind es eben 3604 Sekunden statt 3600. Was soll's?
Wenn's dir nur um Performance und ums Prinzip geht, darfst du hier eben keine Interfaces benutzen. Ich glaube allerdings in der Praxis ist dieses "Problem" vernachlässigbar. Und Nicht-Probleme soll man nicht versuchen zu lösen (siehe Ebenius' Signatur).
Wenn es jetzt aber ein konkretes Performance-Problem gibt (siehe OP), (also eins, das wirklich gelöst werden muss), muss man sich halt was ausdenken. Entweder casten oder (Beispiel) ein generischen Size-Addierer schreiben.
 
Dann können wir ja jetzt dazu übergehen, mir zu erklären, wo der Unterschied zwischen beiden Aufrufen ist... 🙂
 
"Kritisch" bedeutet, dass die Performance einer Zeitkritischen Anwendung sich merklich verschlechtert.

@Ebenius und byto: Es GIBT ja einen Performanceunterschied. Definitiv. Ob der wirklich von den Interfaces stammt, weiß ich nicht. Vielleicht hängt er auch von anderen Sachen ab, die ich nicht berücksichtigt habe (Tipps diesbezüglich wären willkommen). Prinzipiell sind beide Aufrufe virtuell. Allerdings hat der JIT etliche SEHR ausgefeilte Techniken für Inlining, die AFAIK auch davon abhängen könn(t)en, ob "im Moment" eine Klasse geladen ist, die eine andere Klasse erweitert. Die wirkliche Ursache kennt vermutlich nur derjenige, der sich den Code ansieht, den der JIT (und nicht nur der Compiler, sondern wirklich der JIT!) generiert, und den auch noch im jeweiligen Kontext nachvollziehen und richtig interpretieren kann. Das ist nicht so einfach. Jedenfalls kann ich solche Sachen wie http://weblogs.java.net/blog/kohsuke/archive/2008/03/deep_dive_into.html nicht so perfekt nachvollziehen, dass ich da für den Konkreten Fall hilfreiche Informationen rausholen könnte.

Ich werd' nochmal schauen, ob "zugänglichere" Seiten (bin gerade über http://www.ibm.com/developerworks/library/j-jtp12214/ gestolpert) da noch das eine oder andere "Aha!" bringen, aber was auch immer die genaue Ursache ist: In dem Programm, um das es geht, dauert die Ausführung einer Methode über Interfaces 10 Sekunden, und mit casts auf die jeweilige Klasse nur 6 Sekunden - und das ist im Moment ein Problem für mich, das ich gerne lösen (können) würde (falls man es lösen kann, und es nicht "systeminhärent" und durch die Verwendung des Interfaces begründet ist, wie es im Moment noch für mich den Anschein hat)
 
Ebenius hat gesagt.:
Kann mir bitte trotzdem jemand erklären, wieso das einen Unterschied machen soll? Beide Aufrufe sind virtuell. Der Aufwand ist exakt der gleiche!

Code:
class TestA
{
    private int x = 122;

    public int get()
    {
        return x;
    }
}


class TestB
    extends TestA
{
    private int n = 123;

    public int get()
    {
        return n;
    }
}


class TestC
    extends TestB
{
    private int v = 124;

    public int get()
    {
        return v;
    }
}


class InterfaceSpeedTest
{
    public static void main(String args[])
    {
        TestA data = new TestC();
        int[] a = new int[]
            {1000, 5000, 10000, 20000};
        for (int i = 0; i < a.length; i++)
        {
            runA(data, a[i]);
            runB(data, a[i]);
        }
    }


    private static void runA(TestA data, int runs)
    {
        long before = System.currentTimeMillis();
        for (int j = 0; j < 100000; j++)
        {
            for (int i = 0; i < runs; i++)
            {
                data.get();
            }
        }
        long after = System.currentTimeMillis();
        System.out.println(" " + runs + " A " + (after - before));
    }

    private static void runB(TestA data, int runs)
    {
        long before = System.currentTimeMillis();
        for (int j = 0; j < 100000; j++)
        {
            for (int i = 0; i < runs; i++)
            {
                ((TestB)data).get();
            }
        }
        long after = System.currentTimeMillis();
        System.out.println(" " + runs + " B " + (after - before));
    }

}


----

Ausgabe:
 1000 A 1172
 1000 B 1328
 5000 A 5515
 5000 B 6906
 10000 A 11079
 10000 B 13812

wenn ich dagegen wieder ein TestB-Objekt verwende, dann wirds unterschiedlich schnell:

Code:
 1000 A 1188
 1000 B 359
 5000 A 5109
 5000 B 1672
 10000 A 10078
 10000 B 3360

füge ich ganz am Ende der main noch einen unscheinbaren Aufruf
Code:
runB(new TestC(), 1);
hinzu, dann ist die Zeit wieder ausgeglichen, es liegt also eine Optimierung des Compilers vor?
kann der einfach entscheiden 'oh, diese Methode wird anscheinend nur mit TestB aufgerufen, dann änder ich da mal was'?
frech frech

habe dann weiter mit Reflection usw getestest und bin letztlich zu dem gekommen, was Marco13 eben auch geschrieben hat:
"die AFAIK auch davon abhängen könn(t)en, ob "im Moment" eine Klasse geladen ist, die eine andere Klasse erweitert"

bei folgendem Code:

Code:
class InterfaceSpeedTest
{
    public static void main(String args[])
        throws Exception
    {

        Method m = null;
        Method[] ma = InterfaceSpeedTest.class.getDeclaredMethods();
        for (Method mi : ma)
        {
            if (mi.getName().equals("runB"))
            {
                m = mi;
            }
        }


        TestA dataB = new TestB();

        int[] a = new int[]
            {1000, 5000, 10000, 20000};
        for (int i = 0; i < a.length; i++)
        {
            runA(dataB, a[i]);
            runB(dataB, a[i]);

            m.invoke(null, dataB, a[i]);
            // m.invoke(null, dataC, a[i]);
            break;
        }
        System.out.println();
        TestA dataC = (TestA)Class.forName("TestC").newInstance();
        for (int i = 0; i < a.length; i++)
        {
            runA(dataB, a[i]);
            runB(dataB, a[i]);

            m.invoke(null, dataB, a[i]);
            m.invoke(null, dataC, a[i]);
            break;
        }

    }


    private static void runA(TestA data, int runs)
    {
        long before = System.currentTimeMillis();
        int zahl = 0;
        for (int j = 0; j < 100000; j++)
        {
            for (int i = 0; i < runs; i++)
            {
                zahl = data.get();
            }
        }
        long after = System.currentTimeMillis();
        System.out.println(zahl + " " + runs + " A " + (after - before));
    }

    private static void runB(TestA data, int runs)
    {
        long before = System.currentTimeMillis();
        int zahl = 0;
        for (int j = 0; j < 100000; j++)
        {
            for (int i = 0; i < runs; i++)
            {
                zahl = ((TestB)data).get();
            }
        }
        long after = System.currentTimeMillis();
        System.out.println(zahl + " " + runs + " B " + (after - before));
    }

}

----
Ausgabe:
123 1000 A 1188
123 1000 B 437
123 1000 B 297

123 1000 A 1172
123 1000 B 1360
123 1000 B 1734
124 1000 B 1922

wird erst schnell gearbeitet, dann Klasse C initialisiert oder zumindest verwendet und dann gehts auch für KlasseB langsamer

die Reflection-Aufrufe spielen dabei wohl keine Rolle mehr
(edit: halt, wenn ich direkt ein TestC initialisiere und normal runB aufrufe, dann sind auch die ersten Durchläufe langsamer)

----

edit: bestimmt auch wie in maki's Links 😉
 
hdi hat gesagt.:
Klar sind interfaces ein bisschen langsamer, fällt aber in der Praxis nicht auf.

Code:
public interface Searchable{

         public String[] getOptimalPath(); // rekursiv durch DFS
}

public class Tree implements Searchable(){
       ...
}

public class Test{

         public static void main(String[] args){
                 
                Searchable s = new Tree();
                s.setNodes(100000000000);
                s.getOptimalPath(); // Rechenzeit ~ 1 Stunde
                ((Tree)s).getOpimtalPath(); // Rechenzeit ~ 45 Minuten
         }
}

... man kann also pauschal nicht sagen, dass es egal ist. Das Bsp im Code ist übrigens eine Sache
die fast in jedem Programm, dass grössere Mengen an Daten verwaltet, vorkommt. Und das macht
auf jeden Fall einen Unterschied!

PS: Die Zeitangabe ~ 1 Stunde ist vollkommen geraten, ich kann das jetzt nicht abschätzen, könnte
auch nur 15 Mins dauern, vllt aber auch 1 Tag.
dieses Posting ergibt für mich gar keinen Sinn,

wenn man mal weiter annimmt, dass das was hier zu Beginn angenommen wurde, stimmte,
dann hast du da doch nur EINEN Aufruf, der schneller wird, um 0.2 ns,
warum sollte sich das auf die Geschwindigkeit von Millionen inneren Methodenaufrufen auswirken, sofern sie überhaupt vorhanden sind?

und auch bei der Rekursion spielt das keine Rolle, entweder wird in Tree sowieso schon ohne Interface die eigene Klasse aufgerufen oder
es werden irgendwelche Interface-Methoden aufgerufen, aber die sind doch unabhängig von dem einen Aufruf in der main()?!
 
@SlaterB: Ja, nachdem ich eben den vorhin geposteten Link gelesen hatte, und habe dann einen weiteren Test gemacht, der perfekt analog zu deinem ist: Eine weitere Klasse "TestClass2 extends TestClass", und davon einfach nur ein sinnloses kleines Objektchen erstellt, und schon ändert sich das ganze dramatisch:

Vor dem Estellen des Objektes:
-1554051584 20000 interface 3828
-1554051584 20000 class 2890

Nach dem Erstellen des Objektes:
-1554051584 20000 interface 9109
-1554051584 20000 class 10015

Damit wäre die Vermutung

habe dann weiter mit Reflection usw getestest und bin letztlich zu dem gekommen, was Marco13 eben auch geschrieben hat:
"die AFAIK auch davon abhängen könn(t)en, ob "im Moment" eine Klasse geladen ist, die eine andere Klasse erweitert"

"bestätigt" - und wenigstens ist der Unterschied da jetzt nichtmehr 25% :lol:

@Maki: Den newsletter lese ich häufig, und es ist gut möglich, dass die "R" aus dem "AFAIR" oben genau DORT her stammte :roll: aber werd' mir die beiden Artikel nochmal durchlesen - danke.
 
Marco13 hat gesagt.:
@Maki: Den newsletter lese ich häufig, und es ist gut möglich, dass die "R" aus dem "AFAIR" oben genau DORT her stammte :roll: aber werd' mir die beiden Artikel nochmal durchlesen - danke.
bisher tauchte in diesem Topic kein 'AFAIR' auf 😉
 
Marco13 hat gesagt.:
habe dann weiter mit Reflection usw getestest und bin letztlich zu dem gekommen, was Marco13 eben auch geschrieben hat:
"die AFAIK auch davon abhängen könn(t)en, ob "im Moment" eine Klasse geladen ist, die eine andere Klasse erweitert"
Könnte daran liegen, dass sich dadurch der v-table (oder wie auch immer das Ding bei Java heisst) vergrößert.

Wie lasst Ihr die Tests überhaupt laufen (Java Version, Client oder Server VM)?

Hab mir nun auch mal die Mühe gemacht und den Code laufen lassen und komme auf folgendes Ergebnis mit JDK 1.6 Server VM:

Code:
1000 interface 94
1000 class     78
10000 interface 793
10000 class     794
100000 interface 9865
100000 class     7858
 
Ja, ich hatte die beiden Artikel schonmal gelesen - das "K" war ein "weiches K" (eher ein "da war doch was ???:L" :wink: ).

Trotz allem finde ich solche Performanceeinbrüche (und es SIND teiweise wirklich dramatische Einbrüche) durch die Verwendung von Polymorphie und Interfaces irgendwie ... schade :?

@byto: In den Artikeln, auf die Maki verlinkt hat, stehen enige Infos zu "Bi-Morphism" und "Poly-Morphism" - aber das alles ist natürlich schwierig einzuordnen, wenn man es von einem Microbenchmark auf ein "richtiges" Programm übertragen will.

Nur als Demonstration, nochmal das Programm, das SlaterB schon in ähnlicher Form gepostest hatte: Der Unterschied, ob man die (eigentlich völlig irrelevante) Zeile 29 ein- oder auskommentiert, ist schon erschreckend...

Code:
interface TestInterface
{
    int get();
}

class TestClass implements TestInterface
{
    private int n = 123;
    public int get() { return n; }
}

class TestClass2 extends TestClass
{
    private int n = 234;
    public int get() { return n; }
}

class InterfaceSpeedTest
{
    public static void main(String args[])
    {
        TestInterface data[] = createData(50000);
        for (int runs=5000; runs<=10000; runs*=2)
        {
            runInterface(data, runs);
            runClass(data, runs);
        }
        TestClass2 tc2 = new TestClass2(); //----------- The magic line of code....
        for (int runs=5000; runs<=10000; runs*=2)
        {
            runInterface(data, runs);
            runClass(data, runs);
        }

    }

    private static TestInterface[] createData(int size)
    {
        TestInterface data[] = new TestInterface[size];
        for (int i=0; i<data.length; i++)
        {
            data[i] = new TestClass();
        }
        return data;
    }


    private static void runInterface(TestInterface data[], int runs)
    {
        int sum = 0;
        long before = System.currentTimeMillis();
        for (int i=0; i<runs; i++)
        {
            sum += runInterface(data);
        }
        long after = System.currentTimeMillis();
        System.out.println(sum+" "+runs+" interface "+(after-before));
    }

    private static int runInterface(TestInterface data[])
    {
        int sum = 0;
        for (int i=0; i<data.length; i++)
        {
            sum += data[i].get();
        }
        return sum;
    }



    private static void runClass(TestInterface data[], int runs)
    {
        int sum = 0;
        long before = System.currentTimeMillis();
        for (int i=0; i<runs; i++)
        {
            sum += runClass(data);
        }
        long after = System.currentTimeMillis();
        System.out.println(sum+" "+runs+" class     "+(after-before));
    }

    private static int runClass(TestInterface data[])
    {
        int sum = 0;
        for (int i=0; i<data.length; i++)
        {
            sum += ((TestClass)data[i]).get();
        }
        return sum;
    }


}
 
Hast Du das JDK oder das JRE eingebunden in Eclipse? Die Server VM gibts nur im JDK (warum auch immer).
 
Probier doch mal das JDK in der eclipse.ini anzugeben, ime nutzt Eclipse unter Win Standardmässig das JRE, letzteres kennt nur die Client VM.
 
Ja, ich hatte es dann umgestellt, das war ja nicht das Problem (nur dass er das 1. standardmäßig nicht gesetzt hat und 2. TROTZ der expliziten Angabe nicht angenommen hatte), aber es gibt im "richtigen" Programm einige verschiedene Implementierungen/Szenarien zu testen.
Der erste Blick sieht so aus, als ob das schonmal deutlich was bringt (im Mircrobenchmark sind die Zeiten für den class- und den interface-Fall jetzt schonmal praktisch gleich - zumindest konnte ich auf die Schnelle dort keinen Unterschied mehr provozieren...)
 
OK, so als Resümee: Mit dem -server-Flag ist der Unterschied zwischen der Verwendung von Interfaces und dem casten auf die jeweilige Klasse nicht mehr messbar.
Allerdings treten noch andere Ungereimtheiten auf, was die Laufzeiten angeht - und die sind (trotz (oder gerade wegen) der vielen Einsichten und Erkenntnisse die sich aus diesem Thread für mich ergeben haben) absolut unerklärlich, und lassen mich persönlich zu einem Schluss kommen: Man kann (wenn man bestimmte Dinge berücksichtigt) Microbenchmarks für einzelne, "einfache" (speziell "Number-Crunching"-lastige) Methoden erstellen - aber Strukturen, die Vererbung und Polymorphie enthalten, sind (auch wenn diese Dinge eigentlich keinen Einfluß auf den ausgeführten Code haben sollten) praktisch unmicrobenchmarkbar.
 
Status
Nicht offen für weitere Antworten.

Neue Themen


Zurück
Oben