J'ai le code Java suivant avec plusieurs grands tableaux qui ne changent jamais de taille. Il tourne en 1100 ms sur mon ordinateur.
J'ai implémenté le même code en C ++ et utilisé std::vector
.
Le temps de l'implémentation C ++ qui exécute exactement le même code est de 8800 ms sur mon ordinateur. Qu'est-ce que j'ai fait de mal, pour que cela fonctionne lentement?
Fondamentalement, le code effectue les opérations suivantes:
for (int i = 0; i < numberOfCells; ++i) {
h[i] = h[i] + 1;
floodedCells[i] = !floodedCells[i];
floodedCellsTimeInterval[i] = !floodedCellsTimeInterval[i];
qInflow[i] = qInflow[i] + 1;
}
Il parcourt différents tableaux d'une taille d'environ 20000.
Vous pouvez trouver les deux implémentations sous les liens suivants:
- Java: https://ideone.com/R8KqjT
- C ++: https://ideone.com/Lu7RpE
(Sur ideone, je ne pouvais exécuter la boucle que 400 fois au lieu de 2000 fois à cause de la limitation de temps. Mais même ici, il y a une différence de trois fois)
std::vector<bool>
utilise un bit par élément pour économiser de l'espace, ce qui entraîne beaucoup de décalage de bits. Si vous voulez de la vitesse, vous devez vous en tenir à l'écart. Utilisezstd::vector<int>
plutôt.h[i] += 1;
ou (mieux encore)++h[i]
soit plus lisible queh[i] = h[i] + 1;
, je serais un peu surpris de voir une différence de vitesse significative entre eux. Un compilateur peut "comprendre" qu'ils font tous les deux la même chose et générer le même code dans les deux cas (au moins dans la plupart des cas courants).Réponses:
Voici la version C ++ avec les données par nœud rassemblées dans une structure, et un seul vecteur de cette structure utilisé:
exemple en direct
Le temps est maintenant 2x la vitesse de la version Java. (846 contre 1631).
Il y a de fortes chances que le JIT ait remarqué la gravure du cache pour accéder aux données partout et a transformé votre code en un ordre logiquement similaire mais plus efficace.
J'ai également désactivé la synchronisation stdio, car cela n'est nécessaire que si vous mélangez
printf
/scanf
avec C ++std::cout
etstd::cin
. En l'occurrence, vous n'imprimez que quelques valeurs, mais le comportement par défaut de C ++ pour l'impression est trop paranoïaque et inefficace.Si
nEdges
n'est pas une valeur constante réelle, alors les 3 valeurs de «tableau» devront être supprimées dustruct
. Cela ne devrait pas causer un énorme impact sur les performances.Vous pourrez peut-être obtenir une autre amélioration des performances en triant les valeurs dans ce
struct
diminuant la taille, réduisant ainsi l'empreinte mémoire (et en triant également l'accès lorsque cela n'a pas d'importance). Mais je ne suis pas sûr.En règle générale, un seul échec de cache coûte 100 fois plus cher qu'une instruction. Organiser vos données pour avoir la cohérence du cache a beaucoup de valeur.
Si réarranger les données dans un
struct
est infaisable, vous pouvez changer votre itération pour être sur chaque conteneur à son tour.En passant, notez que les versions Java et C ++ présentaient des différences subtiles. Celui que j'ai repéré était que la version Java a 3 variables dans la boucle "for each edge", alors que celle en C ++ n'en avait que 2. J'ai fait correspondre la mienne à Java. Je ne sais pas s'il y en a d'autres.
la source
Oui, le cache dans la version c ++ prend un martèlement. Il semble que le JIT soit mieux équipé pour gérer cela.
Si vous changez l'extérieur
for
de isUpdateNeeded () en extraits de code plus courts. La différence disparaît.L'exemple ci-dessous produit une accélération 4x.
Cela montre à un degré raisonnable que les échecs de cache sont la raison du ralentissement. Il est également important de noter que les variables ne sont pas dépendantes, de sorte qu'une solution threadée est facilement créée.
Commande rétablie
Selon le commentaire de stefans, j'ai essayé de les regrouper dans une structure en utilisant les tailles d'origine. Cela supprime la pression immédiate du cache de la même manière. Le résultat est que la version c ++ (CCFLAG -O3) est environ 15% plus rapide que la version java.
Varning ni court ni joli.
Mon résultat diffère légèrement de Jerry Coffins pour les tailles d'origine. Pour moi, les différences demeurent. Cela pourrait bien être ma version java, 1.7.0_75.
la source
++
aide à quelque titre que ce soit?x = x + 1
semble terriblement maladroit par rapport à++x
.Comme @Stefan l'a deviné dans un commentaire sur la réponse de @ CaptainGiraffe, vous gagnez beaucoup à utiliser un vecteur de structures au lieu d'une structure de vecteurs. Le code corrigé ressemble à ceci:
Compilé avec le compilateur de VC ++ 2015 CTP, en utilisant
-EHsc -O2b2 -GL -Qpar
, j'obtiens des résultats comme:La compilation avec g ++ produit un résultat légèrement plus lent:
Sur le même matériel, en utilisant le compilateur / JVM de Java 8u45, j'obtiens des résultats comme:
C'est environ 35% plus lent que la version de VC ++, et environ 16% plus lent que la version de g ++.
Si nous augmentons le nombre d'itérations aux 2000 souhaités, la différence tombe à seulement 3%, ce qui suggère qu'une partie de l'avantage de C ++ dans ce cas est simplement un chargement plus rapide (un problème éternel avec Java), pas vraiment dans l'exécution elle-même. Cela ne me semble pas surprenant dans ce cas - le calcul mesuré (dans le code affiché) est si trivial que je doute que la plupart des compilateurs puissent faire beaucoup pour l'optimiser.
la source
#pragma omp
, et (peut-être) un peu de travail pour m'assurer que chaque itération de boucle est indépendante. Cela prendrait un travail assez minime pour obtenir une accélération de ~ Nx, où N est le nombre de cœurs de processeur disponibles.Je soupçonne qu'il s'agit d'allocation de mémoire.
Je pense que
Java
saisit un gros bloc contigu au démarrage du programme alors queC++
le système d'exploitation demande des morceaux au fur et à mesure.Pour mettre cette théorie à l'épreuve, j'ai apporté une modification à la
C++
version et elle a soudainement commencé à fonctionner légèrement plus vite que laJava
version:Runtime sans le vecteur de pré-allocation:
Runtime avec le vecteur de pré-allocation:
Runtime pour la
Java
version:la source
FloodIsolation
peuvent encore être attribuées ailleurs.