Wie schnell ist D im Vergleich zu C ++?

133

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;
}
Lars
quelle
3
Ihr Programm hat übrigens einen Fehler in dieser Zeile: avg = avg / N*N(Reihenfolge der Operationen).
Vladimir Panteleev
4
Sie können versuchen, den Code mithilfe von Array- / Vektoroperationen digitalmars.com/d/2.0/arrays.html
Michal Minich
10
Um einen besseren Vergleich zu ermöglichen, sollten Sie dasselbe Compiler-Backend verwenden. Entweder DMD und DMC ++ oder GDC und G ++
he_the_great
1
@Sion Sheevok Leider scheint dmd profiling für Linux nicht verfügbar zu sein? (Bitte korrigieren Sie mich, wenn ich falsch liege , aber wenn ich sage, dass dmd ... trace.defich eine bekomme error: unrecognized file extension def. Und die dmd-Dokumente für optlink erwähnen nur Windows.
Lars
1
Ah, ich habe mich nie um diese .def-Datei gekümmert, die sie ausspuckt. Die Timings befinden sich in der .log-Datei. "Es enthält die Liste der Funktionen in der Reihenfolge, in der der Linker sie verknüpfen soll" - vielleicht hilft das optlink, etwas zu optimieren? Beachten Sie auch, dass "Außerdem unterstützt ld die Standarddateien" * .def ", die wie eine Objektdatei in der Linker-Befehlszeile angegeben werden können, vollständig unterstützt. Sie können also versuchen, trace.def über -L zu übergeben, wenn Sie dies wünschen zu.
Trass3r

Antworten:

64

Kompilieren Sie Ihr D-Programm mit den folgenden DMD-Flags, um alle Optimierungen zu aktivieren und alle Sicherheitsüberprüfungen zu deaktivieren:

-O -inline -release -noboundscheck

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.

Vladimir Panteleev
quelle
3
@CyberShadow: Aber wenn Sie die Sicherheitsüberprüfung entfernen ... verlieren Sie nicht einige wichtige Funktionen von D?
Matthieu M.
33
Sie verlieren Funktionen, die C ++ nie hatte. Die meisten Sprachen geben Ihnen keine Wahl.
Vladimir Panteleev
6
@CyberShadow: Können wir uns das als eine Art Debug gegen Release Build vorstellen?
Francesco
7
@Bernard: In -release ist die Überprüfung der Grenzen für den gesamten Code außer für sichere Funktionen deaktiviert. Um die Überprüfung von Grenzen wirklich auszuschalten, verwenden Sie sowohl -release als auch-noboundscheck.
Michal Minich
5
@ CyberShadow Danke! Mit diesen Flags verbessert sich die Laufzeit erheblich. Jetzt ist D bei 12,9 s. Läuft aber immer noch mehr als dreimal so lange. @Matthieu M. Es würde mir nichts ausmachen, ein Programm mit Boundschecking in Zeitlupe zu testen, und wenn es einmal debuggt ist, lassen Sie es seine Berechnungen ohne Boundschecking durchführen. (Ich mache das gleiche jetzt mit C ++.)
Lars
32

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.

Dsimcha
quelle
1
Ich habe festgestellt, dass eine Ihrer Änderungen ein Wechsel von Division zu Bitverschiebung war. Sollte das nicht etwas sein, was der Compiler tut?
GManNickG
3
@GMan: Ja, wenn der Wert, durch den Sie dividieren, zur Kompilierungszeit bekannt ist. Nein, wenn der Wert nur zur Laufzeit bekannt ist, was der Fall war, in dem ich diese Optimierung vorgenommen habe.
Dsimcha
@dsimcha: Hm. Ich denke, wenn Sie wissen, wie man es schafft, kann es auch der Compiler. Problem mit der Qualität der Implementierung, oder fehlt mir, dass eine Bedingung erfüllt sein muss, die der Compiler nicht beweisen kann, aber Sie wissen? (Ich lerne jetzt D, also sind diese kleinen Dinge über den Compiler plötzlich für mich interessant. :))
GManNickG
13
@GMan: Bitverschiebung funktioniert nur, wenn die Zahl, durch die Sie teilen, eine Zweierpotenz ist. Der Compiler kann dies nicht beweisen, wenn die Nummer nur zur Laufzeit bekannt ist und das Testen und Verzweigen langsamer ist als nur die Verwendung der div-Anweisung. Mein Fall ist ungewöhnlich, da der Wert nur zur Laufzeit bekannt ist, aber ich weiß zur Kompilierungszeit, dass es eine Zweierpotenz sein wird.
Dsimcha
7
Beachten Sie, dass das in diesem Beispiel veröffentlichte Programm keine Zuordnung im zeitaufwändigen Teil vornimmt.
Vladimir Panteleev
27

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.

Andrei Alexandrescu
quelle
4
Ich stimme vollkommen zu. Wie später hinzugefügt, interessiert mich hauptsächlich die Leistung für numerische Berechnungen, bei denen die Schleifenoptimierung wahrscheinlich die wichtigste ist. Welche anderen Optimierungen wären Ihrer Meinung nach für das numerische Rechnen wichtig? Und welche Berechnungen würden sie testen? Ich wäre interessiert, meinen Test zu ergänzen und weitere Tests durchzuführen (wenn sie ungefähr so ​​einfach sind). Aber evtl. das ist eine andere Frage für sich?
Lars
11
Als Ingenieur, der sich in C ++ die Zähne geschnitten hat, sind Sie ein Held von mir. Respektvoll sollte dies jedoch ein Kommentar sein, keine Antwort.
Alan
14

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 ( xsund 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 writeflnmitwriteln

  • Die Importe wurden so geändert, dass sie selektiv sind
  • Verwendet pow operator ( ^^) anstelle der manuellen Multiplikation für den letzten Schritt der Durchschnittsberechnung
  • Entfernt size_typeund entsprechend durch den neuen index_typeAlias ​​ersetzt

... was zu scalar2.cpp( Pastebin ) führt:

    import std.stdio : writeln;
    import std.datetime : Clock, Duration;
    import std.array : uninitializedArray;
    import std.random : uniform;

    alias result_type = long;
    alias value_type = int;
    alias vector_t = value_type[];
    alias index_type = typeof(vector_t.init.length);// Make index integrals portable - Linux is ulong, Win8.1 is uint

    immutable long N = 20000;
    immutable int size = 10;

    // Replaced for loops with appropriate foreach versions
    value_type scalar_product(in ref vector_t x, in ref vector_t y) { // "in" is the same as "const" here
      value_type res = 0;
      for(index_type i = 0; i < size; ++i)
        res += x[i] * y[i];
      return res;
    }

    int main() {
      auto tm_before = Clock.currTime;
      auto countElapsed(in string taskName) { // Factor out printing code
        writeln(taskName, ": ", Clock.currTime - tm_before);
        tm_before = Clock.currTime;
      }

      // 1. allocate and fill randomly many short vectors
      vector_t[] xs = uninitializedArray!(vector_t[])(N);// Avoid default inits of inner arrays
      for(index_type i = 0; i < N; ++i)
        xs[i] = uninitializedArray!(vector_t)(size);// Avoid more default inits of values
      countElapsed("allocation");

      for(index_type i = 0; i < N; ++i)
        for(index_type j = 0; j < size; ++j)
          xs[i][j] = uniform(-1000, 1000);
      countElapsed("random");

      // 2. compute all pairwise scalar products:
      result_type avg = 0;
      for(index_type i = 0; i < N; ++i)
        for(index_type j = 0; j < N; ++j)
          avg += scalar_product(xs[i], xs[j]);
      avg /= N ^^ 2;// Replace manual multiplication with pow operator
      writeln("result: ", avg);
      countElapsed("scalar products");

      return 0;
    }

Nach dem Testen scalar2.d(bei dem die Optimierung der Geschwindigkeit im Vordergrund stand) habe ich aus Neugier die Schleifen maindurch foreachÄquivalente ersetzt und sie scalar3.d( Pastebin ) genannt:

    import std.stdio : writeln;
    import std.datetime : Clock, Duration;
    import std.array : uninitializedArray;
    import std.random : uniform;

    alias result_type = long;
    alias value_type = int;
    alias vector_t = value_type[];
    alias index_type = typeof(vector_t.init.length);// Make index integrals portable - Linux is ulong, Win8.1 is uint

    immutable long N = 20000;
    immutable int size = 10;

    // Replaced for loops with appropriate foreach versions
    value_type scalar_product(in ref vector_t x, in ref vector_t y) { // "in" is the same as "const" here
      value_type res = 0;
      for(index_type i = 0; i < size; ++i)
        res += x[i] * y[i];
      return res;
    }

    int main() {
      auto tm_before = Clock.currTime;
      auto countElapsed(in string taskName) { // Factor out printing code
        writeln(taskName, ": ", Clock.currTime - tm_before);
        tm_before = Clock.currTime;
      }

      // 1. allocate and fill randomly many short vectors
      vector_t[] xs = uninitializedArray!(vector_t[])(N);// Avoid default inits of inner arrays
      foreach(ref x; xs)
        x = uninitializedArray!(vector_t)(size);// Avoid more default inits of values
      countElapsed("allocation");

      foreach(ref x; xs)
        foreach(ref val; x)
          val = uniform(-1000, 1000);
      countElapsed("random");

      // 2. compute all pairwise scalar products:
      result_type avg = 0;
      foreach(const ref x; xs)
        foreach(const ref y; xs)
          avg += scalar_product(x, y);
      avg /= N ^^ 2;// Replace manual multiplication with pow operator
      writeln("result: ", avg);
      countElapsed("scalar products");

      return 0;
    }

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:

  • C ++: clang++ scalar.cpp -o"scalar.cpp.exe" -std=c++11 -O3
  • D: rdmd --compiler=ldc2 -O3 -boundscheck=off <sourcefile>

Ergebnisse

Die Ergebnisse ( Screenshot der Rohkonsolenausgabe ) jeder Version der Quelle lauten wie folgt:

  1. scalar.cpp (Original C ++):

    allocation: 2 ms
    
    random generation: 12 ms
    
    result: 29248300000
    
    time: 2582 ms

    C ++ setzt den Standard auf 2582 ms .

  2. scalar.d (modifizierte OP-Quelle):

    allocation: 5 ms, 293 μs, and 5 hnsecs 
    
    random: 10 ms, 866 μs, and 4 hnsecs 
    
    result: 53237080000
    
    scalar products: 2 secs, 956 ms, 513 μs, and 7 hnsecs 

    Dies lief ~ 2957 ms . Langsamer als die C ++ - Implementierung, aber nicht zu viel.

  3. scalar2.d (Änderung des Index- / Längentyps und nicht initialisierte Array-Optimierung):

    allocation: 2 ms, 464 μs, and 2 hnsecs
    
    random: 5 ms, 792 μs, and 6 hnsecs
    
    result: 59
    
    scalar products: 1 sec, 859 ms, 942 μs, and 9 hnsecs

    Mit anderen Worten, ~ 1860 ms . Bisher liegt dies an der Spitze.

  4. scalar3.d (foreaches):

    allocation: 2 ms, 911 μs, and 3 hnsecs
    
    random: 7 ms, 567 μs, and 8 hnsecs
    
    result: 189
    
    scalar products: 2 secs, 182 ms, and 366 μs

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

Erich Gubler
quelle
8

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

Trass3r
quelle
7

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.

Jonathan M Davis
quelle
4
Ja, ich wäre überrascht, wenn die Eingabe / Ausgabe in beiden Sprachen signifikant schneller wäre oder wenn reine Mathematik in beiden Sprachen signifikant schneller wäre, aber Zeichenfolgenoperationen, Speicherverwaltung und einige andere Dinge könnten leicht eine Sprache zum Leuchten bringen.
Max Lybbert
1
Es ist einfach besser (schneller) als C ++ - iostreams. Dies ist jedoch in erster Linie ein Problem bei der Implementierung der Bibliothek (bei allen bekannten Versionen der beliebtesten Anbieter).
Ben Voigt
4

Sie können schreiben, dass C-Code D ist. Soweit dies schneller ist, hängt es von vielen Dingen ab:

  • Welchen Compiler verwenden Sie?
  • Welche Funktion verwenden Sie?
  • wie aggressiv Sie optimieren

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.

BCS
quelle
3

Scheint ein Problem mit der Qualität der Implementierung zu sein. Zum Beispiel habe ich Folgendes getestet:

import std.datetime, std.stdio, std.random;

version = ManualInline;

immutable N = 20000;
immutable Size = 10;

alias int value_type;
alias long result_type;
alias value_type[] vector_type;

result_type scalar_product(in vector_type x, in vector_type y)
in
{
    assert(x.length == y.length);
}
body
{
    result_type result = 0;

    foreach(i; 0 .. x.length)
        result += x[i] * y[i];

    return result;
}

void main()
{   
    auto startTime = Clock.currTime();

    // 1. allocate vectors
    vector_type[] vectors = new vector_type[N];
    foreach(ref vec; vectors)
        vec = new value_type[Size];

    auto time = Clock.currTime() - startTime;
    writefln("allocation: %s ", time);
    startTime = Clock.currTime();

    // 2. randomize vectors
    foreach(ref vec; vectors)
        foreach(ref e; vec)
            e = uniform(-1000, 1000);

    time = Clock.currTime() - startTime;
    writefln("random: %s ", time);
    startTime = Clock.currTime();

    // 3. compute all pairwise scalar products
    result_type avg = 0;

    foreach(vecA; vectors)
        foreach(vecB; vectors)
        {
            version(ManualInline)
            {
                result_type result = 0;

                foreach(i; 0 .. vecA.length)
                    result += vecA[i] * vecB[i];

                avg += result;
            }
            else
            {
                avg += scalar_product(vecA, vecB);
            }
        }

    avg = avg / (N * N);

    time = Clock.currTime() - startTime;
    writefln("scalar products: %s ", time);
    writefln("result: %s", avg);
}

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

GManNickG
quelle
1
Ihre Timings sind bedeutungslos, es sei denn, Sie vergleichen auch Ihre C ++ - Timings.
Verlangsamte
3
@ Daniel: Du verpasst den Punkt. Es sollte die D-Optimierungen isoliert demonstrieren, und zwar zu dem Schluss, dass ich festgestellt habe: "Der Compiler fügt diese einfache Funktion also nicht einmal ein, was meiner Meinung nach klar ist, dass es so sein sollte." Ich versuche sogar, es mit C ++ zu vergleichen, wie ich im ersten Satz klar festgestellt habe : "Scheint ein Problem mit der Qualität der Implementierung zu sein."
GManNickG
Ah wahr, sorry :). Sie werden auch feststellen, dass der DMD-Compiler die Schleifen überhaupt nicht vektorisiert.
Verlangsamte