Ich mag einige Funktionen von D, wäre aber interessiert, wenn sie mit einer Laufzeitstrafe verbunden sind?
Zum Vergleich habe ich ein einfaches Programm implementiert, das Skalarprodukte vieler Kurzvektoren sowohl in C ++ als auch in D berechnet. Das Ergebnis ist überraschend:
- D: 18,9 s [endgültige Laufzeit siehe unten]
- C ++: 3,8 s
Ist C ++ wirklich fast fünfmal so schnell oder habe ich im D-Programm einen Fehler gemacht?
Ich habe C ++ mit g ++ -O3 (gcc-snapshot 2011-02-19) und D mit dmd -O (dmd 2.052) auf einem moderaten Linux-Desktop kompiliert. Die Ergebnisse sind über mehrere Läufe reproduzierbar und Standardabweichungen vernachlässigbar.
Hier das C ++ - Programm:
#include <iostream>
#include <random>
#include <chrono>
#include <string>
#include <vector>
#include <array>
typedef std::chrono::duration<long, std::ratio<1, 1000>> millisecs;
template <typename _T>
long time_since(std::chrono::time_point<_T>& time) {
long tm = std::chrono::duration_cast<millisecs>( std::chrono::system_clock::now() - time).count();
time = std::chrono::system_clock::now();
return tm;
}
const long N = 20000;
const int size = 10;
typedef int value_type;
typedef long long result_type;
typedef std::vector<value_type> vector_t;
typedef typename vector_t::size_type size_type;
inline value_type scalar_product(const vector_t& x, const vector_t& y) {
value_type res = 0;
size_type siz = x.size();
for (size_type i = 0; i < siz; ++i)
res += x[i] * y[i];
return res;
}
int main() {
auto tm_before = std::chrono::system_clock::now();
// 1. allocate and fill randomly many short vectors
vector_t* xs = new vector_t [N];
for (int i = 0; i < N; ++i) {
xs[i] = vector_t(size);
}
std::cerr << "allocation: " << time_since(tm_before) << " ms" << std::endl;
std::mt19937 rnd_engine;
std::uniform_int_distribution<value_type> runif_gen(-1000, 1000);
for (int i = 0; i < N; ++i)
for (int j = 0; j < size; ++j)
xs[i][j] = runif_gen(rnd_engine);
std::cerr << "random generation: " << time_since(tm_before) << " ms" << std::endl;
// 2. compute all pairwise scalar products:
time_since(tm_before);
result_type avg = 0;
for (int i = 0; i < N; ++i)
for (int j = 0; j < N; ++j)
avg += scalar_product(xs[i], xs[j]);
avg = avg / N*N;
auto time = time_since(tm_before);
std::cout << "result: " << avg << std::endl;
std::cout << "time: " << time << " ms" << std::endl;
}
Und hier die D-Version:
import std.stdio;
import std.datetime;
import std.random;
const long N = 20000;
const int size = 10;
alias int value_type;
alias long result_type;
alias value_type[] vector_t;
alias uint size_type;
value_type scalar_product(const ref vector_t x, const ref vector_t y) {
value_type res = 0;
size_type siz = x.length;
for (size_type i = 0; i < siz; ++i)
res += x[i] * y[i];
return res;
}
int main() {
auto tm_before = Clock.currTime();
// 1. allocate and fill randomly many short vectors
vector_t[] xs;
xs.length = N;
for (int i = 0; i < N; ++i) {
xs[i].length = size;
}
writefln("allocation: %i ", (Clock.currTime() - tm_before));
tm_before = Clock.currTime();
for (int i = 0; i < N; ++i)
for (int j = 0; j < size; ++j)
xs[i][j] = uniform(-1000, 1000);
writefln("random: %i ", (Clock.currTime() - tm_before));
tm_before = Clock.currTime();
// 2. compute all pairwise scalar products:
result_type avg = cast(result_type) 0;
for (int i = 0; i < N; ++i)
for (int j = 0; j < N; ++j)
avg += scalar_product(xs[i], xs[j]);
avg = avg / N*N;
writefln("result: %d", avg);
auto time = Clock.currTime() - tm_before;
writefln("scalar products: %i ", time);
return 0;
}
c++
performance
runtime
d
Lars
quelle
quelle
avg = avg / N*N
(Reihenfolge der Operationen).dmd ... trace.def
ich eine bekommeerror: unrecognized file extension def
. Und die dmd-Dokumente für optlink erwähnen nur Windows.Antworten:
Kompilieren Sie Ihr D-Programm mit den folgenden DMD-Flags, um alle Optimierungen zu aktivieren und alle Sicherheitsüberprüfungen zu deaktivieren:
BEARBEITEN : Ich habe Ihre Programme mit g ++, dmd und gdc ausprobiert. dmd bleibt zwar zurück, aber gdc erreicht eine Leistung, die sehr nahe an g ++ liegt. Die Befehlszeile, die ich verwendet habe, war
gdmd -O -release -inline
(gdmd ist ein Wrapper um gdc, der dmd-Optionen akzeptiert).In der Assembler-Liste sieht es so aus, als ob weder dmd noch gdc inline sind
scalar_product
, aber g ++ / gdc hat MMX-Anweisungen ausgegeben, sodass die Schleife möglicherweise automatisch vektorisiert wird.quelle
Eine große Sache, die D verlangsamt, ist eine unterdurchschnittliche Implementierung der Speicherbereinigung. Benchmarks, die den GC nicht stark belasten, zeigen eine sehr ähnliche Leistung wie C- und C ++ - Code, der mit demselben Compiler-Backend kompiliert wurde. Benchmarks, die den GC stark belasten, zeigen, dass D eine miserable Leistung erbringt. Seien Sie jedoch versichert, dass dies ein einzelnes (wenn auch schwerwiegendes) Problem der Implementierungsqualität ist und keine eingebaute Garantie für Langsamkeit. Außerdem bietet D die Möglichkeit, die GC zu deaktivieren und die Speicherverwaltung in leistungskritischen Bits zu optimieren, während Sie sie weiterhin in den weniger leistungskritischen 95% Ihres Codes verwenden.
Ich habe in letzter Zeit einige Anstrengungen unternommen, um die GC-Leistung zu verbessern, und die Ergebnisse waren ziemlich dramatisch, zumindest bei synthetischen Benchmarks. Hoffentlich werden diese Änderungen in eine der nächsten Versionen integriert und das Problem behoben.
quelle
Dies ist ein sehr lehrreicher Thread, danke für die ganze Arbeit an das OP und die Helfer.
Ein Hinweis: Bei diesem Test wird weder die allgemeine Frage der Abstraktion / Feature-Strafe noch die der Backend-Qualität bewertet. Es konzentriert sich auf praktisch eine Optimierung (Schleifenoptimierung). Ich denke, es ist fair zu sagen, dass das Backend von gcc etwas raffinierter ist als das von dmd, aber es wäre ein Fehler anzunehmen, dass die Lücke zwischen ihnen für alle Aufgaben gleich groß ist.
quelle
Scheint definitiv ein Problem mit der Qualität der Implementierung zu sein.
Ich habe einige Tests mit dem OP-Code durchgeführt und einige Änderungen vorgenommen. Ich eigentlich D geht schneller bekam für LDC / Klirren ++, unter der Annahme arbeitet , dass Arrays müssen dynamisch zugewiesen werden (
xs
und die damit verbundenen Skalare). Siehe unten für einige Zahlen.Fragen an das OP
Ist beabsichtigt, dass für jede Iteration von C ++ derselbe Startwert verwendet wird, nicht jedoch für D?
Konfiguration
Ich habe die ursprüngliche D-Quelle (synchronisiert
scalar.d
) optimiert , um sie zwischen Plattformen portierbar zu machen. Dies beinhaltete nur das Ändern des Typs der Nummern, die für den Zugriff auf und das Ändern der Größe von Arrays verwendet wurden.Danach habe ich folgende Änderungen vorgenommen:
Wird verwendet
uninitializedArray
, um Standardeinstellungen für Skalare in xs zu vermeiden (wahrscheinlich der größte Unterschied). Dies ist wichtig, da D normalerweise standardmäßig alles stillschweigend einschaltet, was C ++ nicht tut.Ausklammern Code Druck und ersetzt
writefln
mitwriteln
^^
) anstelle der manuellen Multiplikation für den letzten Schritt der Durchschnittsberechnungsize_type
und entsprechend durch den neuenindex_type
Alias ersetzt... was zu
scalar2.cpp
( Pastebin ) führt:Nach dem Testen
scalar2.d
(bei dem die Optimierung der Geschwindigkeit im Vordergrund stand) habe ich aus Neugier die Schleifenmain
durchforeach
Äquivalente ersetzt und siescalar3.d
( Pastebin ) genannt:Ich habe jeden dieser Tests mit einem LLVM-basierten Compiler kompiliert, da LDC hinsichtlich der Leistung die beste Option für die D-Kompilierung zu sein scheint. Bei meiner x86_64 Arch Linux-Installation habe ich die folgenden Pakete verwendet:
clang 3.6.0-3
ldc 1:0.15.1-4
dtools 2.067.0-2
Ich habe die folgenden Befehle verwendet, um sie zu kompilieren:
clang++ scalar.cpp -o"scalar.cpp.exe" -std=c++11 -O3
rdmd --compiler=ldc2 -O3 -boundscheck=off <sourcefile>
Ergebnisse
Die Ergebnisse ( Screenshot der Rohkonsolenausgabe ) jeder Version der Quelle lauten wie folgt:
scalar.cpp
(Original C ++):C ++ setzt den Standard auf 2582 ms .
scalar.d
(modifizierte OP-Quelle):Dies lief ~ 2957 ms . Langsamer als die C ++ - Implementierung, aber nicht zu viel.
scalar2.d
(Änderung des Index- / Längentyps und nicht initialisierte Array-Optimierung):Mit anderen Worten, ~ 1860 ms . Bisher liegt dies an der Spitze.
scalar3.d
(foreaches):~ 2182 ms ist langsamer als
scalar2.d
, aber schneller als die C ++ - Version.Fazit
Mit den richtigen Optimierungen ging die D-Implementierung mit den verfügbaren LLVM-basierten Compilern tatsächlich schneller als die entsprechende C ++ - Implementierung. Die derzeitige Lücke zwischen D und C ++ für die meisten Anwendungen scheint nur auf Einschränkungen der aktuellen Implementierungen zu beruhen.
quelle
dmd ist die Referenzimplementierung der Sprache, und daher wird die meiste Arbeit in das Frontend gesteckt, um Fehler zu beheben, anstatt das Backend zu optimieren.
"in" ist in Ihrem Fall schneller, da Sie dynamische Arrays verwenden, die Referenztypen sind. Mit ref führen Sie eine weitere Indirektionsebene ein (die normalerweise verwendet wird, um das Array selbst und nicht nur den Inhalt zu ändern).
Vektoren werden normalerweise mit Strukturen implementiert, bei denen const ref vollkommen sinnvoll ist. Unter smallptD vs. smallpt finden Sie ein reales Beispiel mit vielen Vektoroperationen und Zufälligkeiten.
Beachten Sie, dass 64-Bit auch einen Unterschied machen kann. Ich habe einmal übersehen, dass auf x64 gcc 64-Bit-Code kompiliert, während dmd immer noch standardmäßig 32 ist (wird sich ändern, wenn der 64-Bit-Codegen ausgereift ist). Es gab eine bemerkenswerte Beschleunigung mit "dmd -m64 ...".
quelle
Ob C ++ oder D schneller ist, hängt wahrscheinlich stark davon ab, was Sie tun. Ich würde denken, dass beim Vergleich von gut geschriebenem C ++ mit gut geschriebenem D-Code im Allgemeinen entweder eine ähnliche Geschwindigkeit vorliegt oder C ++ schneller ist, aber was der jeweilige Compiler optimieren kann, könnte neben der Sprache einen großen Effekt haben selbst.
Es gibt jedoch einige Fälle, in denen D eine gute Chance hat, C ++ für Geschwindigkeit zu schlagen. Das wichtigste, was mir in den Sinn kommt, ist die Verarbeitung von Zeichenfolgen. Dank der Array-Slicing-Funktionen von D können Strings (und Arrays im Allgemeinen) viel schneller verarbeitet werden als in C ++. Für D1 ist der XML-Prozessor von Tango extrem schnell , vor allem dank der Array-Slicing-Funktionen von D (und hoffentlich wird D2 einen ähnlich schnellen XML-Parser haben, sobald der derzeit für Phobos bearbeitete abgeschlossen ist). Ob D oder C ++ schneller sein wird, hängt also stark davon ab, was Sie tun.
Nun, ich bin überrascht, dass Sie in diesem speziellen Fall einen solchen Geschwindigkeitsunterschied sehen, aber es ist die Art von Dingen, von denen ich erwarten würde, dass sie sich verbessern, wenn sich dmd verbessert. Die Verwendung von gdc könnte zu besseren Ergebnissen führen und wäre wahrscheinlich ein genauerer Vergleich der Sprache selbst (und nicht des Backends), da sie auf gcc basiert. Aber es würde mich überhaupt nicht überraschen, wenn es eine Reihe von Dingen gibt, die getan werden könnten, um den von dmd generierten Code zu beschleunigen. Ich denke nicht, dass es viele Fragen gibt, dass gcc zu diesem Zeitpunkt reifer als dmd ist. Und Code-Optimierungen sind eine der Hauptfrüchte der Code-Reife.
Letztendlich kommt es darauf an, wie gut dmd für Ihre spezielle Anwendung funktioniert, aber ich stimme zu, dass es auf jeden Fall schön wäre zu wissen, wie gut C ++ und D im Allgemeinen verglichen werden. Theoretisch sollten sie ziemlich gleich sein, aber es hängt wirklich von der Implementierung ab. Ich denke, dass ein umfassender Satz von Benchmarks erforderlich wäre, um wirklich zu testen, wie gut sich die beiden derzeit vergleichen.
quelle
Sie können schreiben, dass C-Code D ist. Soweit dies schneller ist, hängt es von vielen Dingen ab:
Unterschiede in der ersten sind nicht fair zu ziehen. Die zweite könnte C ++ einen Vorteil verschaffen, da es, wenn überhaupt, weniger schwere Funktionen hat. Der dritte ist der Spaß: D-Code ist in gewisser Weise einfacher zu optimieren, da er im Allgemeinen leichter zu verstehen ist. Es hat auch die Fähigkeit, ein hohes Maß an generativer Programmierung durchzuführen, so dass Dinge wie ausführlicher und sich wiederholender, aber schneller Code in kürzeren Formen geschrieben werden können.
quelle
Scheint ein Problem mit der Qualität der Implementierung zu sein. Zum Beispiel habe ich Folgendes getestet:
Mit
ManualInline
definiert bekomme ich 28 Sekunden, aber ohne bekomme ich 32. Der Compiler fügt diese einfache Funktion also nicht einmal ein, was meiner Meinung nach klar ist, dass es so sein sollte.(Meine Kommandozeile ist
dmd -O -noboundscheck -inline -release ...
.)quelle