Leistungsunterschied zwischen Mehrfachzeiger-Dereferenzierungen und Referenzen

8

Das war eine Interviewfrage, die ich vor ein paar Monaten von mir gestellt habe:

Welche der folgenden Funktionen wird schneller ausgeführt, Foo1 oder Foo2 ?

%Vor%

Beachten Sie, dass dies aus dem Speicher stammt, sodass ich mich nicht genau an den Code erinnere, aber die allgemeine Idee ist dieselbe. Eine Funktion verwendet den Zeiger, während die andere eine Referenz verwendet (sie kann einen Zeiger auf ein Array wie im obigen Code haben, aber ich erinnere mich nicht genau). Ich sagte I am not sure, and will have to profile the code to find out, but if I had to guess Foo2 'may' be faster . Sie waren nicht beeindruckt ...

Das hat mich ein paar Mal hier und da geärgert, als ich Code durchlief (oder schrieb) und würde mich fragen, was ich in einem solchen Fall tun sollte.

Ich weiß das ...

  1. Dies ist eine Mikrooptimierung
  2. Der Compiler wird es wahrscheinlich optimieren
  

BEARBEITEN: Ich habe den Code leicht geändert, so dass jetzt nach einem NULL-Zeiger gesucht wird.

    
Samaursa 28.04.2011, 16:39
quelle

8 Antworten

6

Ich denke, das ist eine ziemlich interessante Frage, und ich habe eine Menge Spekulationen darüber gesehen, was ein Compiler tun könnte, aber ich wollte genauer hinsehen und es herausfinden. Also nahm ich das Programm von e.James und führte es durch GCC, um die Versammlung zu bekommen. Ich sollte sagen, dass ich Assembly nicht wirklich kenne, also korrigiert mich jemand, wenn ich falsch liege, aber ich denke, wir können vernünftigerweise schlussfolgern, was vor sich geht. :)

Kompilieren mit -O0 (keine Optimierung)

Für Foo1 sehen wir, dass der Array-Offset vor jedem Funktionsaufruf berechnet wird:

%Vor%

Das ist für alle sechs Methodenaufrufe, nur mit verschiedenen Methodennamen. Foo2 hat ein wenig Setup-Code, um die Referenz

zu erhalten %Vor%

Und dann sechs von diesen, die aussehen wie nur ein Stapelzeiger und ein Funktionsaufruf:

%Vor%

So viel, was wir ohne Optimierung erwarten würden. Die Ausgabe war

%Vor%

Kompilieren mit -O1 (minimale Optimierung)

Foo1 ist ein wenig effizienter, addiert aber jedes Mal den Array-Offset:

%Vor%

Foo2 looks speichert den Wert von ebx ( addl (%edi), %ebx ) und ruft dann diese Aufrufe auf:

%Vor%

Die Zeiten hier waren

%Vor%

Kompilieren mit -O2 (moderate Optimierung)

Beim Kompilieren mit -O2 hat GCC das ganze Ding losgeworden und jeder Aufruf von Foo1 oder Foo2 führte nur dazu, dass 594 zu dummy hinzugefügt wurde (99 Inkremente * 6 Aufrufe = 594 Inkremente):

%Vor%

Die Methoden des Objekts wurden nicht aufgerufen, obwohl diese Methoden im Code verblieben sind. Wie zu erwarten, waren die Zeiten hier

%Vor%

Ich denke, das sagt uns, dass Foo2 ein wenig schneller ohne Optimierungen ist, aber es ist wirklich ein strittiger Punkt, weil der Compiler sich nur ein paar Sekunden zwischen dem Stack und den Registern bewegt, sobald er zu optimieren beginnt.

    
Steve Blackwell 28.04.2011, 20:33
quelle
4

Streng genommen und ohne Optimierungen würde ich sagen, dass Foo2 schneller ist, weil Foo1 jedes Mal das Indtrection-Ptr berechnen muss, aber das wird nirgends passieren.

Ich würde sagen, der Compiler wird es optimieren und es gleich lassen.
Offensichtlich gibt es genug Platz für den Compiler, i und array ändern sich nicht für den gesamten Block bei jeder Iteration, so dass es den Zeiger in ein Register setzen könnte, genau so, wie er es für den Referenz.

    
Arkaitz Jimenez 28.04.2011 16:40
quelle
3

Wahrscheinlich wird der Compiler sie auf Grund der gemeinsamen Unterausdrücke in jeder Zeile optimieren. Keine Garantie, obwohl.

Es gibt keine rationalen Schlussfolgerungen, die Sie mit den heutigen Compiler und Prozessoren durch Lehnstuhl-Argumentation erreichen können. Der einzige Weg, es zu wissen, ist es zu versuchen und es zu tun. Wenn jemand das in der Interviewantwort nicht klarstellte, wäre es ein automatisches fail von mir.

    
Mark Ransom 28.04.2011 16:59
quelle
2

Weder wird ein vernünftiger Compiler sie gleichwertig machen. Sie sprechen nicht über NRVO in den Tiefen der Template-Metaprogrammierung hier, es ist eine grundlegende und einfache Optimierung mit Common Subexpression Elimination, die extrem häufig und relativ einfach zu tun ist, und der veröffentlichte Code hat triviale Komplexität, so dass es überwältigend wahrscheinlich ist, dass Compiler wird eine solche Optimierung durchführen.

    
Puppy 28.04.2011 16:56
quelle
2

Nur für den Fall, dass jemand bezweifelt, dass der Compiler zum selben Ergebnis führt, hier ist ein schneller & amp; schmutziges Testprogramm:

%Vor%

Die Ergebnisse liegen immer innerhalb weniger Millisekunden zueinander:

  

Foo1: 15437
  Foo2: 15484

    
e.James 28.04.2011 17:08
quelle
1

Imho, die Frage, welche Version schneller ist, ist irrelevant. Das Aufrufen von 6 verschiedenen Methoden nacheinander auf einem Objekt ist ein OO-Design-Geruch. Das Objekt sollte wahrscheinlich eine Methode bieten, die all dies tut.

    
fredoverflow 28.04.2011 17:29
quelle
0

Foo2 hat diese zusätzliche Objekterstellung, aber ansonsten sollte die Kompilierung sie zu dem gleichen machen

    
Keerigan 28.04.2011 16:43
quelle
0

Ich mag diese Frage und kann mir nicht anders helfen, als zu antworten, obwohl ich weiß, dass ich falsch liege. Trotzdem denke ich, Foo1 wird schneller sein.

Mein dummer Grund? Nun, ich sehe, dass Foo2 eine Objektreferenz erstellt, die Adresse von "array" bekommt und dann Aufrufe an seine Methoden gemacht werden.

Aber in Foo1 benutzt es die Adresse direkt, dereferenziert es, geht zum Objektspeicher und ruft die Funktion direkt auf. Kein unnötiger Objektreferenz in Foo1 wie Foo2 erstellt. Und wir wissen nicht, was die Tiefe des Arrays in Bezug auf die Vererbung ist und wie viele Basisklassenkonstruktoren aufgerufen werden, um sogar die Objektreferenz zu erhalten, was zusätzliche Zeit in Anspruch nimmt. Also ich denke, Foo1 ist marginal schneller. Plz korrigiere mich, weil ich zuversichtlich bin, dass ich falsch liege. Prost!

    
aeon 28.04.2011 16:50
quelle

Tags und Links