130 votes

Quelle est la rapidité de D par rapport à C++ ?

J'aime certaines caractéristiques de D, mais je serais intéressé s'ils sont accompagnés d'une pénalité de temps d'exécution ?

Pour comparer, j'ai implémenté un programme simple qui calcule les produits scalaires de plusieurs vecteurs courts à la fois en C++ et en D. Le résultat est surprenant :

  • D : 18,9 s [voir ci-dessous pour le temps de fonctionnement final].
  • C++ : 3,8 s

Le C++ est-il vraiment presque cinq fois plus rapide ou ai-je fait une erreur dans le D ou ai-je fait une erreur dans le programme D ?

J'ai compilé C++ avec g++ -O3 (gcc-snapshot 2011-02-19) et D avec dmd -O (dmd 2.052) sur un bureau linux modérément récent. Les résultats sont reproductibles sur plusieurs exécutions et les écarts types négligeables.

Voici le programme C++ :

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

Et voici la version D :

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

64voto

CyberShadow Points 13244

Pour activer toutes les optimisations et désactiver tous les contrôles de sécurité, compilez votre programme D avec les drapeaux DMD suivants :

-O -inline -release -noboundscheck

EDIT : J'ai essayé vos programmes avec g++, dmd et gdc. dmd est effectivement à la traîne, mais gdc atteint des performances très proches de g++. La ligne de commande que j'ai utilisée était gdmd -O -release -inline (gdmd est une enveloppe autour de gdc qui accepte les options de dmd).

En regardant le listing de l'assembleur, on dirait que ni dmd ni gdc n'ont mis en ligne scalar_product mais g++/gdc a émis des instructions MMX, donc ils pourraient auto-vectoriser la boucle.

32voto

dsimcha Points 32831

L'une des principales causes de ralentissement de D est l'implémentation médiocre de la collecte des déchets. Les benchmarks qui ne sollicitent pas fortement la GC montrent des performances très similaires à celles du code C et C++ compilé avec le même compilateur. Les benchmarks qui sollicitent fortement la GC montreront que D a des performances abyssales. Mais rassurez-vous, il s'agit d'un problème unique (bien que grave) de qualité de mise en œuvre, et non d'une garantie de lenteur. De plus, D vous donne la possibilité de ne pas utiliser la GC et de régler la gestion de la mémoire dans les bits critiques pour les performances, tout en continuant à l'utiliser dans les 95% moins critiques pour les performances de votre code.

J'ai a fait des efforts pour améliorer les performances de la GC ces derniers temps et les résultats ont été plutôt spectaculaires, du moins sur les bancs d'essai synthétiques. Espérons que ces changements seront intégrés dans l'une des prochaines versions et qu'ils atténueront le problème.

27voto

C'est un fil de discussion très instructif, merci pour tout le travail au PO et aux aides.

Remarque : ce test n'évalue pas la question générale de la pénalité d'abstraction/de fonctionnalité ni même celle de la qualité du backend. Il se concentre sur une seule optimisation (l'optimisation des boucles). Je pense qu'il est juste de dire que le backend de gcc est un peu plus raffiné que celui de dmd, mais ce serait une erreur de supposer que l'écart entre eux est aussi grand pour toutes les tâches.

8voto

Trass3r Points 1505

Dmd est l'implémentation de référence du langage et, par conséquent, la plupart des travaux sont effectués sur le front-end pour corriger les bogues plutôt que d'optimiser le back-end.

"in" est plus rapide dans votre cas car vous utilisez des tableaux dynamiques qui sont des types de référence. Avec ref, vous introduisez un autre niveau d'indirection (qui est normalement utilisé pour modifier le tableau lui-même et pas seulement son contenu).

Les vecteurs sont généralement implémentés avec des structs où const ref est parfaitement logique. Voir smallptD vs. smallpt pour un exemple concret comportant de nombreuses opérations vectorielles et un caractère aléatoire.

Notez que le 64-Bit peut également faire une différence. Une fois, j'ai remarqué que sur x64, gcc compile du code 64 bits alors que dmd utilise par défaut 32 bits (cela changera lorsque le code 64 bits arrivera à maturité). Il y avait une accélération remarquable avec "dmd -m64 ...".

7voto

Jonathan M Davis Points 19569

La rapidité du C++ ou du D dépend fortement de ce que vous faites. Je pense que si l'on compare un code C++ bien écrit à un code D bien écrit, la vitesse sera généralement similaire, ou le C++ sera plus rapide, mais ce que le compilateur particulier parvient à optimiser peut avoir un effet important, indépendamment du langage lui-même.

Cependant, il y a son quelques cas où D a de bonnes chances de battre C++ en vitesse. Le principal cas qui me vient à l'esprit est le traitement des chaînes de caractères. Grâce aux capacités de découpage en tableaux de D, les chaînes de caractères (et les tableaux en général) peuvent être traités beaucoup plus rapidement que ce que l'on peut faire en C++. Pour D1, Le processeur XML de Tango est extrêmement rapide Grâce aux capacités de découpage en tableaux de D (et, espérons-le, D2 disposera d'un analyseur XML tout aussi rapide, une fois que celui sur lequel on travaille actuellement pour Phobos aura été achevé). Donc, en fin de compte, la question de savoir si D ou C++ sera plus rapide dépendra beaucoup de ce que vous faites.

Maintenant, je Je suis Je suis surpris que vous voyiez une telle différence de vitesse dans ce cas particulier, mais c'est le genre de chose que je m'attendrais à voir s'améliorer avec l'amélioration de dmd. Utiliser gdc pourrait donner de meilleurs résultats et serait probablement une comparaison plus proche du langage lui-même (plutôt que du backend) étant donné qu'il est basé sur gcc. Mais cela ne me surprendrait pas du tout s'il y a un certain nombre de choses qui pourraient être faites pour accélérer le code que dmd génère. Je ne pense pas qu'il y ait beaucoup de doute sur le fait que gcc est plus mature que dmd à ce stade. Et l'optimisation du code est l'un des principaux fruits de la maturité du code.

En fin de compte, ce qui compte, c'est la performance de dmd pour votre application particulière, mais je suis d'accord qu'il serait vraiment bien de savoir comment C++ et D se comparent en général. En théorie, ils devraient être à peu près identiques, mais cela dépend vraiment de l'implémentation. Je pense qu'un ensemble complet de benchmarks serait nécessaire pour vraiment tester la comparaison entre les deux actuellement.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X