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;
    }


}
 
S

SlaterB

Gast
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
 

Der Müde Joe

Top Contributor
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
 

hdi

Top Contributor
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
 

Ebenius

Top Contributor
Ich vertrau dem Test nicht. Da ist ja der JIT-Compiler noch nichtmal fertig mit kompilieren. :)
 

Marco13

Top Contributor
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... :(
 
M

maki

Gast
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.
 
S

SlaterB

Gast
> 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
 

hdi

Top Contributor
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.
 
M

maki

Gast
. 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 ;)
 

hdi

Top Contributor
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.
 

byte

Top Contributor
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.
 
M

maki

Gast
>> 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.
 

hdi

Top Contributor
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.
 

byte

Top Contributor
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:
 

hdi

Top Contributor
und ein return boolean, wie es bei DFS der Fall ist, dauert genauso "lang" wie ein return 100.
 

tfa

Top Contributor
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?
 

Ebenius

Top Contributor
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
 

hdi

Top Contributor
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.
 

Marco13

Top Contributor
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...
 

Ebenius

Top Contributor
Kann mir bitte trotzdem jemand erklären, wieso das einen Unterschied machen soll? Beide Aufrufe sind virtuell. Der Aufwand ist exakt der gleiche!
 

byte

Top Contributor
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.
 

tfa

Top Contributor
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.
 

Ebenius

Top Contributor
Dann können wir ja jetzt dazu übergehen, mir zu erklären, wo der Unterschied zwischen beiden Aufrufen ist... :)
 

Marco13

Top Contributor
"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)
 
S

SlaterB

Gast
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 ;)
 
S

SlaterB

Gast
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()?!
 

Marco13

Top Contributor
@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.
 
S

SlaterB

Gast
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 ;)
 

byte

Top Contributor
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
 

Marco13

Top Contributor
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;
    }


}
 

byte

Top Contributor
Hast Du das JDK oder das JRE eingebunden in Eclipse? Die Server VM gibts nur im JDK (warum auch immer).
 
M

maki

Gast
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.
 

Marco13

Top Contributor
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...)
 

Marco13

Top Contributor
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.
Ähnliche Java Themen
  Titel Forum Antworten Datum
S Alle Methodenaufrufe eines Threads notieren..? Allgemeine Java-Themen 7
E Tool um Methodenaufrufe aus bestimmten Pkg zu finden Allgemeine Java-Themen 2
G Methodenaufrufe wie state maschine Allgemeine Java-Themen 4
M Java Klassen und Veerbung mit nichtnachvollziebarer Methodenaufrufe :( Allgemeine Java-Themen 13
V Methodenaufrufe in ein Array strecken Allgemeine Java-Themen 9
B Methodenaufrufe aus String Allgemeine Java-Themen 9
P gegenseitige Methodenaufrufe zweier Klassen Allgemeine Java-Themen 14
P Methodenaufrufe Allgemeine Java-Themen 3
padde479 Anzahl Methodenaufrufe Allgemeine Java-Themen 7
Zrebna Wieso sind eigentlich JUnit-Tests in src/test/java platziert - nur Konvention? Allgemeine Java-Themen 7
Sachinbhatt Sind alle Methoden in Java implizit virtuell Allgemeine Java-Themen 2
berserkerdq2 Labels in IJVM sind keine lokalen Variablen oder? Allgemeine Java-Themen 2
O Produziert das Tool "jpackage" (ab JDK 14) .exe Dateien, die auf einer Zielumgebung ohne JRE lauffähig sind ?` Allgemeine Java-Themen 7
KeTho1712 Java Swing: JTable standardmäßig füllen, sodass bei Start bereits Datensätze gespeichert sind Allgemeine Java-Themen 1
W Wieviele Threads sind sinnvoll? Allgemeine Java-Themen 8
L Bewerberaufgaben sind nur Zeitverschwendung... Allgemeine Java-Themen 10
W Was genau sind IOTools? Kann ich stattdessen nicht die Scanner Klasse verwenden? Allgemeine Java-Themen 3
D Was sind Bibliotheken in Java/Pyhton? Allgemeine Java-Themen 1
F Java Installationen sind unterschiedlich Allgemeine Java-Themen 11
N Was sind Logger in Java? (bzgl. SonarLint) Allgemeine Java-Themen 3
Kaffeevertilger Warum nullable Booleans doof sind ... Allgemeine Java-Themen 22
Sogomn OOP Sind Helferklassen böse? Allgemeine Java-Themen 3
A Datentypen Gregorian Calendar - 2 Daten sind gleich?? Allgemeine Java-Themen 3
Bluedaishi Dateien löschen die älter als das aktuelle Datum sind Allgemeine Java-Themen 9
U Set erklären dass objekte gleich sind Allgemeine Java-Themen 12
G Generics sind zu streng - oder ich zu naiv? Allgemeine Java-Themen 3
G Wie groß sind die Adressen in Java? Allgemeine Java-Themen 4
E Funktion sperren bis Unterfunktionen ferig sind Allgemeine Java-Themen 3
S Hash-Bereiche erstellen die gleichverteilt sind..? Allgemeine Java-Themen 8
L Sicherstellen das 2x die gleichen Daten unter bestimmten Keys enthalten sind. Allgemeine Java-Themen 6
S ThreadPoolExecutor: wie stelle ich fest dass meine Threads im Pool mit ihrer Arbeit fertig sind? Allgemeine Java-Themen 3
H Prüfen, ob doppete Werte in int-Array vorhanden sind Allgemeine Java-Themen 16
N URL einlesen -> Daten sind nicht vollständig bzw. korrekt Allgemeine Java-Themen 9
G Datei einlesen: Umlaute sind Fragezeichen Allgemeine Java-Themen 23
aokai Testen von Klassen die abhängig von Stdlibs URL sind Allgemeine Java-Themen 3
G Sind Applets noch uptodate Allgemeine Java-Themen 24
D Problem mit Tooltips und JFrame (Tooltips sind zu kurz!) Allgemeine Java-Themen 4
T Überprüfen ob zwei Farben ähnlich sind Allgemeine Java-Themen 14
M Sind Streams asynchron? Allgemeine Java-Themen 2
G Prüfen ob Ziffern einer Zahl pandigital sind? Allgemeine Java-Themen 15
O Warten bis alle gestarteten Threads beendet sind? Allgemeine Java-Themen 6
reibi JVM fragen welche Apps geladen sind Allgemeine Java-Themen 7
M wie dateien speichern damit sie platform unabhängig sind? Allgemeine Java-Themen 2
D gewisse Zeichen sind nach dem entschlüsseln anders Allgemeine Java-Themen 2
K Wie gut sind java.util - ADTs ? Allgemeine Java-Themen 2
Bleiglanz Benchmarks sind sehr schwierig Allgemeine Java-Themen 2
P Woher weiß ein Programm wo seine Ressourcen sind? Allgemeine Java-Themen 4
U wie groß sind Verzeichnisse Allgemeine Java-Themen 11
N String überprüfen ob nur Ziffern enthalten sind!! Allgemeine Java-Themen 8
S Was sind eigentlich Java Beans? Allgemeine Java-Themen 2
kodela Datenübergabe über Buttons Allgemeine Java-Themen 8
W Jar-File Start nur über Terminal Allgemeine Java-Themen 13
A ByteArray über Socket Allgemeine Java-Themen 3
berserkerdq2 Text über einen Shape anzeigen (Scenebuilder) Allgemeine Java-Themen 1
I 2D-Grafik Vektor-Grafik über die Zwischenablage nach Adobe Illustrator transferieren Allgemeine Java-Themen 8
TheSkyRider Methode über DataInputStream "auslösen" Allgemeine Java-Themen 6
I OpenPDF erzeugt riesige PDFs, wenn Grafiken über PdfGraphics2D#drawImage gezeichnet werden Allgemeine Java-Themen 1
T Etikettendrucker über TCP-IP Allgemeine Java-Themen 1
Encera Gleichzeitiges Ausführen und verbinden von 2 Java-Klassen über die Eingabeaufforderung und Eclipse Allgemeine Java-Themen 21
B HTTP Allgemeine Fragen über Suchmaschine nutzen mit Java Allgemeine Java-Themen 20
torresbig Klasse mit extends Calendar über Methoden ändern (Hirnblockade) Allgemeine Java-Themen 7
B Liste ändern während Iteration über Diese? Allgemeine Java-Themen 16
Master3000 Java Konsole über Buffered Reader Zeilenweise auslesen ? Allgemeine Java-Themen 26
J Daten über serielle Schnittstelle empfangen Allgemeine Java-Themen 4
L Aufwandsabschätzung: Android-App Aufnahmefunktion (foto) und zweiter Ebene über dem Foto (teiltransparent) Allgemeine Java-Themen 6
M Registry Autostart Eintrag mit Java erstellen (über Windows cmd) Allgemeine Java-Themen 7
OSchriever Programm über Linux-Kommandozeile ausführen Allgemeine Java-Themen 20
J Namen von Methoden über Reguläre Ausdrücke bearbeiten Allgemeine Java-Themen 6
M Schnelleres Speichern von XML-Daten über URLConnection Allgemeine Java-Themen 4
M Keine weitere Eingabe in der Main möglich. Eventueller Ansatz über while. Allgemeine Java-Themen 8
Drachenbauer Wie kann eine vorgegebene Farbe über einen String erkannt werden? Allgemeine Java-Themen 11
W Variablenübergabe über mehrere Klassen Allgemeine Java-Themen 4
N Über einen Button in JavaFX ein Event über eine Pipeline schicken(Netty) Allgemeine Java-Themen 1
M Threads über Kommandozeile Allgemeine Java-Themen 5
david19 Software AE über Domain laufen lassen Allgemeine Java-Themen 0
Q Selbständig ActionEvent auslösen zum Daten senden über serielle Schnittstelle Allgemeine Java-Themen 7
T Problem mit externen Datenbankzugriff über SSH Tunnel Allgemeine Java-Themen 4
K Auf Dateiverzeichnis extern zugreifen (evtl über XML??) Allgemeine Java-Themen 22
C VisualVM oder Jconsole über Jolokia-Proxy Allgemeine Java-Themen 0
G USB-Pins über Java ansteuern Allgemeine Java-Themen 8
Ernesto95 Best Practice Localization über ResourceBundle Allgemeine Java-Themen 6
C Classpath Neue Klasse über einen Button ausführen Allgemeine Java-Themen 3
C Auslesen auslösen über Button-Click Allgemeine Java-Themen 8
M Fragen beantworten über Textfeldeingabe Allgemeine Java-Themen 5
AssELAss Best Practice Checksumme über jede Spalte zweier Tabellen und vergleichen Allgemeine Java-Themen 3
T Strings über Bluetooth zwischen PC,µc oder Samrtphone senden und empfangen Allgemeine Java-Themen 0
F Try/catch über ganze Klasse Allgemeine Java-Themen 9
kodela Eigenartige Datumsberechnung über GregorianCalendar Allgemeine Java-Themen 15
HarleyDavidson Best Practice Integer-Zahlenfolge über mehrere Programmstarts Allgemeine Java-Themen 7
T .jar über cmd ausführen (später dann batch) Allgemeine Java-Themen 6
F In OSX: Java-Programm über URI-Scheme aufrufen mit Parameter? Allgemeine Java-Themen 0
C Input/Output Zip Files über Socket senden und empfangen Allgemeine Java-Themen 6
M WebService - Zugriff auf Webservice Methode über Browser Allgemeine Java-Themen 1
C .jar File lässt sich nur über Konsole öffnen Allgemeine Java-Themen 1
L Videodateien über Java öffnen unabhängig vom Format Allgemeine Java-Themen 4
Thallius Hash über serialisiertes Objekt? Allgemeine Java-Themen 3
A Threads Lock über mehrere Abschnitte in verschiedenen Methoden Allgemeine Java-Themen 5
T Daten über port abfangen mit proxy server Allgemeine Java-Themen 12
M Eingabe von Arrays geht über gewünschte Anzahl hinaus Allgemeine Java-Themen 2
L Prüfen, ob Programm über 32bit oder 64bit Java ausgeführt wird Allgemeine Java-Themen 4

Ähnliche Java Themen

Neue Themen


Oben