Welche dieser beiden Methoden ist in C effizienter? Und wie wäre es mit:
pow(x,3)
vs.
x*x*x // etc?
c++
c
optimization
Jamylak
quelle
quelle
x
Integral oder Gleitkomma?Antworten:
Ich habe den Leistungsunterschied zwischen
x*x*...
vspow(x,i)
für smalli
mit diesem Code getestet :Ergebnisse sind:
Beachten Sie, dass ich das Ergebnis jeder Pow-Berechnung akkumuliere, um sicherzustellen, dass der Compiler es nicht entfernt.
Wenn ich die
std::pow(double, double)
Version benutze undloops = 1000000l
, bekomme ich:Dies ist auf einem Intel Core Duo mit Ubuntu 9.10 64bit. Kompiliert mit gcc 4.4.1 mit -o2 Optimierung.
In C ist ja
x*x*x
also schneller alspow(x, 3)
, weil es keinepow(double, int)
Überlastung gibt. In C ++ wird es ungefähr gleich sein. (Vorausgesetzt, die Methodik in meinen Tests ist korrekt.)Dies ist eine Antwort auf den Kommentar von An Markm:
Selbst wenn eine
using namespace std
Direktive ausgegeben wurde und der zweite Parameterpow
ein istint
, wird diestd::pow(double, int)
Überladung von<cmath>
anstelle von::pow(double, double)
von aufgerufen<math.h>
.Dieser Testcode bestätigt dieses Verhalten:
quelle
std::pow
8 * -Schleifenzeiten auf (für Exponenten> 2), es sei denn, Sie verwenden-fno-math-errno
. Dann kann es den Pow-Call aus der Schleife ziehen, wie ich es mir vorgestellt habe. Ich denke, da errno ein globaler Thread ist, erfordert die Thread-Sicherheit, dass pow aufgerufen wird, um errno möglicherweise mehrmals zu setzen ... exp = 1 und exp = 2 sind schnell, da der pow-Aufruf mit nur-O3
.. ( mit - aus der Schleife gehoben wird) ffast-math , es macht die Summe von 8 auch außerhalb der Schleife.)pow
aus der Schleife gehobenen Anruf übereinstimmen , sodass dort ein großer Fehler vorliegt. Es sieht auch so aus, als würden Sie hauptsächlich die Latenz der FP-Addition testen, da alle Tests in derselben Zeitspanne ausgeführt werden. Sie würden erwartentest5
, langsamer zu sein alstest1
, aber es ist nicht. Die Verwendung mehrerer Akkumulatoren würde die Abhängigkeitskette aufteilen und die Latenz verbergen.pow
auf einen sich ständig ändernden Wert festzulegen (um zu verhindern, dass der wiederholte Pow-Ausdruck herausgehoben wird).Das ist die falsche Frage. Die richtige Frage wäre: "Welches ist für menschliche Leser meines Codes leichter zu verstehen?"
Wenn Geschwindigkeit wichtig ist (später), fragen Sie nicht, sondern messen Sie. (Messen Sie vorher, ob die Optimierung tatsächlich einen spürbaren Unterschied macht.) Schreiben Sie den Code bis dahin so, dass er am einfachsten zu lesen ist.
Bearbeiten
Nur um dies zu verdeutlichen (obwohl dies bereits hätte sein sollen): Durchbruchbeschleunigungen entstehen normalerweise durch die Verwendung besserer Algorithmen , die Verbesserung der Datenlokalität, die Reduzierung des dynamischen Speichers , die Vorberechnung von Ergebnissen usw. Sie kommen selten vor Mikrooptimierende Einzelfunktionsaufrufe , und wo sie dies tun, tun sie dies an sehr wenigen Stellen , die nur durch sorgfältige (und zeitaufwändige) Profilerstellung gefunden werden können. Meistens können sie durch sehr nicht intuitive Funktionen beschleunigt werden Dinge (wie das Einfügen
noop
Aussagen), und was eine Optimierung für eine Plattform ist, ist manchmal eine Pessimierung für eine andere (weshalb Sie messen müssen, anstatt zu fragen, weil wir Ihre Umgebung nicht vollständig kennen / haben).Lassen Sie mich dies noch einmal unterstreichen: Selbst in den wenigen Anwendungen, in denen solche Dinge wichtig sind, spielen sie an den meisten Stellen, an denen sie verwendet werden, keine Rolle, und es ist sehr unwahrscheinlich, dass Sie die Stellen finden, an denen sie wichtig sind, indem Sie sich den Code ansehen. Sie müssen die Hot Spots wirklich zuerst identifizieren , da sonst die Optimierung des Codes nur Zeitverschwendung ist .
Selbst wenn eine einzelne Operation (wie das Berechnen des Quadrats eines Werts) 10% der Ausführungszeit der Anwendung beansprucht (was IME ziemlich selten ist), und selbst wenn die Optimierung 50% der für diese Operation erforderlichen Zeit spart (was IME ist) sogar viel, viel seltener), Sie haben dafür gesorgt, dass die Anwendung nur 5% weniger Zeit in Anspruch nimmt .
Ihre Benutzer benötigen eine Stoppuhr, um dies überhaupt zu bemerken. (Ich denke, in den meisten Fällen bleibt etwas unter 20% Beschleunigung für die meisten Benutzer unbemerkt. Und das sind vier solcher Stellen, die Sie finden müssen.)
quelle
x*x
oderx*x*x
wird schneller sein alspow
, dapow
muss sich mit dem allgemeinen Fall befassen, währendx*x
spezifisch ist. Sie können auch den Funktionsaufruf und dergleichen weglassen.Wenn Sie jedoch feststellen, dass Sie auf diese Weise eine Mikrooptimierung durchführen, müssen Sie einen Profiler erstellen und ernsthafte Profilerstellungen durchführen. Die überwältigende Wahrscheinlichkeit ist, dass Sie niemals einen Unterschied zwischen den beiden bemerken würden.
quelle
x*x*x
gegen Doublestd::pow(double base, int exponent)
in einer Zeitschleife getestet und kann keinen statistisch bedeutsamen Leistungsunterschied feststellen.Ich habe mich auch über das Leistungsproblem gewundert und gehofft, dass dies vom Compiler basierend auf der Antwort von @EmileCormier optimiert wird. Ich befürchtete jedoch, dass der von ihm angezeigte Testcode es dem Compiler weiterhin ermöglichen würde, den Aufruf von std :: pow () zu optimieren, da im Aufruf jedes Mal dieselben Werte verwendet wurden, wodurch der Compiler die Ergebnisse und speichern konnte Verwenden Sie es erneut in der Schleife - dies würde die nahezu identischen Laufzeiten für alle Fälle erklären. Also habe ich es mir auch angesehen.
Hier ist der Code, den ich verwendet habe (test_pow.cpp):
Dies wurde kompiliert mit:
Grundsätzlich ist der Unterschied das Argument, dass std :: pow () der Schleifenzähler ist. Wie ich befürchtet habe, ist der Leistungsunterschied ausgeprägt. Ohne das Flag -O2 waren die Ergebnisse auf meinem System (Arch Linux 64-Bit, g ++ 4.9.1, Intel i7-4930):
Bei der Optimierung waren die Ergebnisse gleichermaßen beeindruckend:
Es sieht also so aus, als würde der Compiler zumindest versuchen, den Fall std :: pow (x, 2) zu optimieren, nicht jedoch den Fall std :: pow (x, 3) (es dauert ~ 40-mal länger als der Fall std :: pow (x, 2) Fall). In allen Fällen schnitt die manuelle Erweiterung besser ab - insbesondere beim Power 3-Gehäuse (60-mal schneller). Dies ist auf jeden Fall zu beachten, wenn Sie std :: pow () mit ganzzahligen Potenzen größer als 2 in einer engen Schleife ausführen ...
quelle
Am effizientesten ist es, das exponentielle Wachstum der Multiplikationen zu berücksichtigen. Überprüfen Sie diesen Code auf p ^ q:
quelle
Wenn der Exponent konstant und klein ist, erweitern Sie ihn und minimieren Sie die Anzahl der Multiplikationen. (Zum Beispiel
x^4
ist nicht optimalx*x*x*x
, abery*y
woy=x*x
. Undx^5
isty*y*x
woy=x*x
. Und so weiter.) Für konstante ganzzahlige Exponenten schreiben Sie einfach das optimierte Formular bereits aus; Bei kleinen Exponenten ist dies eine Standardoptimierung, die durchgeführt werden sollte, unabhängig davon, ob der Code profiliert wurde oder nicht. Das optimierte Formular ist in so vielen Fällen schneller, dass es sich grundsätzlich immer lohnt, es zu tun.(Wenn Sie Visual C ++ verwenden,
std::pow(float,int)
führt es die Optimierung durch , auf die ich anspreche, wobei die Reihenfolge der Operationen mit dem Bitmuster des Exponenten zusammenhängt. Ich kann jedoch nicht garantieren, dass der Compiler die Schleife für Sie abrollt, sodass es sich dennoch lohnt, dies zu tun es von Hand.)Übrigens
pow
hat eine (nicht) überraschende Tendenz, auf den Profiler-Ergebnissen aufzutauchen. Wenn Sie es nicht unbedingt benötigen (dh der Exponent ist groß oder keine Konstante) und sich überhaupt Gedanken über die Leistung machen, schreiben Sie am besten den optimalen Code und warten Sie, bis der Profiler Ihnen dies mitteilt (überraschenderweise) ) Zeit verschwenden, bevor man weiter nachdenkt. (Die Alternative besteht darin, anzurufenpow
und sich vom Profiler mitteilen zu lassen, dass es (nicht überraschend) Zeitverschwendung ist. Sie können diesen Schritt durch intelligentes Ausführen unterbinden.)quelle
Ich war mit einem ähnlichen Problem beschäftigt und bin ziemlich verwirrt über die Ergebnisse. Ich berechnete x⁻³ / ² für die Newtonsche Gravitation in einer Situation mit n Körpern (Beschleunigung durch einen anderen Massenkörper M, der sich in einem Abstandsvektor d befindet):
a = M G d*(d²)⁻³/²
(wobei d² das Punktprodukt (Skalarprodukt) von d für sich ist), und ich dachte, das BerechnenM*G*pow(d2, -1.5)
wäre einfacher alsM*G/d2/sqrt(d2)
Der Trick ist, dass dies für kleine Systeme zutrifft, aber mit zunehmender Größe der Systeme
M*G/d2/sqrt(d2)
effizienter wird und ich nicht verstehe, warum sich die Größe des Systems auf dieses Ergebnis auswirkt, da das Wiederholen des Vorgangs mit verschiedenen Daten dies nicht tut. Es ist, als ob es mögliche Optimierungen gäbe, wenn das System wächst, die aber mit nicht möglich sindpow
quelle