Pourquoi est-ce tellement plus lent en C ++?

J’ai converti cette méthode simple de C # en C ++. Il lit une table de chemins et remplit une liste de listes d’ints (ou un vecteur de vecteurs d’ints).

Un exemple de ligne de la table des chemins serait quelque chose comme

0 12 5 16 n 

Je me rends compte qu’il y a de meilleures façons de le faire en général, mais pour l’instant, je veux juste savoir pourquoi mon code C ++ prend autant de temps. Par exemple, 10 minutes au lieu de 10 secondes avec la version C #. Voici mon code C ++. Je devine que j’ai fait quelque chose de radicalement faux.

 //Parses the text path vector into the engine void Level::PopulatePathVectors(ssortingng pathTable) { // Read the file line by line. ifstream myFile(pathTable); for (unsigned int i = 0; i < nodes.size(); i++) { pathLookupVectors.push_back(vector<vector>()); for (unsigned int j = 0; j < nodes.size(); j++) { string line; if (getline(myFile, line)) //Enter if a line is read successfully { stringstream ss(line); istream_iterator begin(ss), end; pathLookupVectors[i].push_back(vector(begin, end)); } } } myFile.close(); } 

Voici la version C #:

 private void PopulatePathLists(ssortingng pathList) { // Read the file and display it line by line. StreamReader streamReader = new StreamReader(pathList); for (int i = 0; i < nodes.Count; i++) { pathLookupLists.Add(new List<List>()); for (int j = 0; j < nodes.Count; j++) { string str = streamReader.ReadLine(); pathLookupLists[i].Add(new List()); //For every ssortingng (list of ints) - put each one into these lists int count = 0; ssortingng tempSsortingng = ""; while (str[count].ToSsortingng() != "n") //While character does not equal null terminator { if (str[count].ToSsortingng() == " ") //Character equals space, set the temp ssortingng //as the node index, and move on { pathLookupLists[i][j].Add(Convert.ToInt32(tempSsortingng)); tempSsortingng = ""; } else //If characters are adjacent, put them together { tempSsortingng = tempSsortingng + str[count]; } count++; } } } streamReader.Close(); } 

Désolé, c’est tellement spécifique, mais je suis perplexe.

EDIT – Beaucoup de personnes ont déclaré avoir testé ce code, et cela ne leur prend que quelques secondes. Tout ce que je sais, c’est que si je commente l’appel de cette fonction, le programme se charge en quelques secondes. Avec l’appel de fonction, cela prend 5 minutes. Presque exactement. Je suis vraiment perplexe. Quel pourrait être le problème?

Voici le PathTable qu’il utilise.

EDIT – J’ai essayé d’exécuter la fonction dans un programme seul et cela a pris quelques secondes, mais j’ai bien peur de ne pas savoir assez pour savoir comment résoudre ce problème. Évidemment ce n’est pas le code. Qu’est ce que ça pourrait être? J’ai vérifié où il était appelé pour voir s’il y avait plusieurs appels, mais ce n’est pas le cas. C’est dans un constructeur du niveau du jeu et cela ne s’appelle qu’une fois.

EDIT – Je comprends que le code n’est pas le meilleur possible, mais ce n’est pas le problème ici. Il court rapidement – environ 3 secondes et c’est bien pour moi. Le problème que j’essaie de résoudre est la raison pour laquelle cela prend tellement plus de temps dans le projet.

EDIT – J’ai commenté tout le code du jeu en dehors de la boucle de jeu principale. J’ai placé la méthode dans la section initialize du code qui est exécuté une fois au démarrage. Hormis quelques méthodes pour configurer une fenêtre, son fonctionnement est à peu près le même que celui du programme avec ONLY la seule méthode utilisée, mais cela prend TOUJOURS environ 5 minutes à s’exécuter. Maintenant, je sais que cela n’a rien à voir avec les dépendances de pathLookupVectors. De plus, je sais que l’ordinateur ne commence pas à écrire sur le disque dur car, pendant que le programme lent ralentit l’exécution de la méthode, je peux ouvrir une autre instance de Visual Studio et exécuter le programme à méthode unique en même temps, ce qui termine l’opération. en secondes. Je me rends compte que le problème peut provenir de certains parameters de base, mais je ne suis pas expérimenté. Je vous prie de m’excuser si cela finit par en être la raison. Je ne comprends toujours pas pourquoi cela prend tellement plus de temps.

D’après votre mise à jour, il est assez clair que la fonction que vous avez publiée ne cause pas le problème de performances. Par conséquent, bien qu’il existe de nombreuses façons de l’optimiser, il semble que cela ne va pas vous aider.

Je suppose que vous pouvez reproduire ce problème de performance chaque fois que vous exécutez votre code, n’est-ce pas? Ensuite, j’aimerais vous suggérer de faire les tests suivants:

  • si vous comstackz votre programme en mode débogage (c.-à-d. sans optimisation), recomstackz-le pour le relâcher (optimisations complètes, favorisant la vitesse) et voyez si cela fait une différence.

  • Pour vérifier si vous passez plus de temps sur cette fonction suspectée, vous pouvez append des instructions printf au début et à la fin de la fonction incluant des horodatages. S’il ne s’agit pas d’une application de console mais d’une application d’interface graphique et de printfs, n’écrivez rien, écrivez dans un fichier journal. Si vous êtes sous Windows, vous pouvez également utiliser OutputDebugSsortingng et utiliser un débogueur pour capturer les printfs. Si vous êtes sous Linux, vous pouvez écrire dans le journal système à l’aide de syslog .

  • Utilisez un profileur de code source pour déterminer où est tout ce temps passé. Si la différence entre appeler cette fonction ou non est de plusieurs minutes, un profileur vous renseignera sûrement sur ce qui se passe. Si vous utilisez Windows, Very Sleepy est un bon choix. Si vous utilisez Linux, vous pouvez utiliser OProfile .

Mise à jour : Donc, vous dites qu’une version est rapide. Cela signifie probablement que les fonctions de bibliothèque que vous utilisez dans cette fonction ont une implémentation de débogage lente. Le TSL est connu pour être comme ça.

Je suis sûr que vous devez déboguer d’autres parties de votre application et vous ne voulez pas attendre toutes ces minutes pour que cette fonction se termine en mode débogage. La solution à ce problème consiste à générer votre projet en mode de publication, mais de modifier la configuration de la version de la manière suivante:

  • désactivez les optimisations uniquement pour les fichiers que vous souhaitez déboguer (assurez-vous que les optimisations restnt activées au moins pour le fichier doté de la fonction lente). Pour désactiver les optimisations sur un fichier, sélectionnez le fichier dans l’explorateur de solutions, cliquez avec le bouton droit de la souris, sélectionnez Propriétés, puis sélectionnez Propriétés de configuration | C / C ++ / Optimization. Examinez comment tous les éléments de cette page sont définis pour la génération Debug et copiez tous ceux de votre version Release. Répétez cette procédure pour tous les fichiers que vous souhaitez mettre à la disposition du débogueur.

  • activer les informations de débogage (le fichier pdb) à générer. Pour ce faire, sélectionnez le projet en haut de l’Explorateur de solutions, cliquez avec le bouton droit de la souris et sélectionnez Propriétés. Cliquez ensuite sur Propriétés de configuration | Éditeur de liens | Débogage et copiez tous les parameters de la version Debug dans la version Release.

Avec les modifications ci-dessus, vous serez en mesure de déboguer les parties du binary d’édition qui ont été configurées comme ci-dessus, exactement comme vous le faites dans la version de débogage.

Une fois le débogage terminé, vous devrez bien sûr réinitialiser tous ces parameters.

J’espère que ça aide.

J’ai profilé le code avec Very Sleepy ( Visual C ++ 2010, Windows XP 32 bits). Je ne sais pas à quel point mes données d’entrée étaient similaires, mais voici quand même les résultats:

39% du temps a été passé dans basic_istream :: operator >>

12% basic_iostream :: basic_iostream

9% opérateur +

8% _Mutex :: Mutex

5% getline

5% basic_ssortingngbuf :: _ Init

4% locale :: _ Locimp :: _ Addfac

4% vecteur :: réserve

4% basic_ssortingng :: assign

3% opérateur supprimer

2% basic_Streambuf :: basic_streambuf

1% Wcsxfrm

5% autres fonctions

Certains des éléments semblent provenir d’appels en ligne, il est donc un peu difficile de dire d’où ils proviennent. Mais vous pouvez toujours avoir l’idée. La seule chose qui devrait faire I / O ici est getline et cela ne prend que 5%. Le rest provient des opérations de stream et de chaîne. Les stream C ++ sont lents comme l’enfer.

La boucle while de votre code semble être très longue et fastidieuse, car elle fait des choses inutiles:

Un code équivalent simple et rapide serait ceci:

 int result; ssortingngstream ss(line); while ( ss >> result ) //reads all ints untill it encounters non-int { pathLookupVectors[i][j].push_back(result); } 

En C ++, cette boucle est également idiomatique. Ou au lieu de cette boucle manuelle, vous pourriez écrire avec std::copy 1 :

 std::copy(std::istream_iterator( ss ), std::istream_iterator(), std::back_inserter(pathLookupVectors[i][j])); 

1. Il est tiré du commentaire de @ David.

Ou même mieux si vous faites cela, quand vous push_back le vecteur lui-même:

  if (getline(myFile, line)) //enter if a line is read successfully { ssortingngstream ss(line); std::istream_iterator begin(ss), end; pathLookupVectors[i].push_back(vector(begin, end)); } 

Terminé!

Je ne sais pas exactement ce qui se passe ici, mais je vois quelques façons d’optimiser votre code. Si cela ne vous amène pas là-bas, alors il pourrait se passer autre chose.


Quelle est la taille de vos cordes? Au fur et à mesure que vous les transmettez dans votre version C ++, vous effectuez des copies car vous “passez par valeur”. Essayez de le passer par référence constante:

 void Level::PopulatePathVectors(const ssortingng &pathTable) 

Cela transmet l’object par référence, ce qui signifie qu’il ne s’agit pas d’une copie. Ensuite, il est de coutume de le rendre const pour s’assurer qu’il ne soit pas modifié dans votre fonction.


Utilisez .append ou += pour étendre tempSsortingng . Je crois que vous .append un nouvel object chaîne, puis que vous remplacez l’ancien par juste + , tandis que += et .append vont modifier l’object actuel à la place:

 tempSsortingng.append(line[count]); 

Vous pouvez également améliorer davantage les performances en déclarant vos variables en haut, puis en les réaffectant. Cela les empêchera d’être recréé à chaque fois. Par exemple, mettez ssortingng line; avant votre boucle, car elle sera écrasée de toute façon.

Vous pouvez faire cela à quelques endroits, comme avec tempSsortingng .

Voici quelques choses que je n’ai vu personne mentionner. Ils sont quelque peu vagues, mais étant incapables de reproduire des choses, il est difficile d’entrer dans les détails.

Profil du pauvre homme.

Pendant que le code est en cours d’exécution, continuez à l’interrompre. Habituellement, vous verrez le même cadre de stack encore et encore.

Commencez à commenter des choses. Si vous commentez votre fractionnement et qu’il se termine instantanément, vous verrez clairement par où commencer.

Une partie du code est dépendante, mais vous pouvez lire le fichier complet en mémoire, puis effectuer l’parsing syntaxique afin de créer une séparation évidente entre les endroits où il passe son temps. Si les deux finissent rapidement de manière indépendante, alors c’est probablement une interaction.

Mise en mémoire tampon.

Je n’ai vu aucune mise en mémoire tampon sur vos lectures. Cela devient particulièrement important si vous écrivez quelque chose sur le disque. Le arm sur votre disque va et vient entre votre emplacement de lecture, puis votre emplacement d’écriture, etc.

Bien que vous n’ayez pas l’air d’écrire ici, il est possible que votre programme principal utilise davantage de mémoire. Il est possible qu’après que vous ayez atteint le niveau de l’eau élevé, le système d’exploitation commence à paginer une partie de la mémoire sur le disque. Vous allez battre lorsque vous lisez ligne par ligne pendant la pagination.

D’habitude, je vais configurer une simple interface d’iterator pour vérifier que tout fonctionne. Ensuite, écrivez un décorateur autour de lui pour lire 500 lignes à la fois. Les stream standard comportent également certaines options de mise en mémoire tampon, qu’il peut être préférable d’utiliser. Je vais deviner que leurs valeurs par défaut sont plutôt conservasortingces.

Réserve.

std::vector::push_back fonctionne mieux lorsque vous utilisez également std::vector::reserve . Si vous pouvez tirer le meilleur parti de la mémoire disponible avant d’entrer dans une boucle serrée, vous gagnez. Vous n’avez même pas besoin de savoir exactement combien, devinez.

Vous pouvez également battre std::vector::resize avec cela, car std::vector::resize utilise alloc et que std::vector::push_back utilisera realloc

Ce dernier bit est contesté, bien que je l’aie lu autrement. Je n’ai aucune raison de douter que je me trompe, bien que je devrai faire plus de recherches pour confirmer ou infirmer.

Néanmoins, push_back peut être plus rapide si vous utilisez reserve avec ce dernier.

Fractionnement des cordes.

Je n’ai jamais vu de solution iterator C ++ aussi performante pour traiter les fichiers gb +. Je n’ai pas essayé celui-là spécifiquement, cependant. J’imagine pourquoi ils ont tendance à faire beaucoup de petites allocations.

Voici une référence avec ce que j’utilise habituellement.

Scinder un tableau de caractères en deux tableaux de caractères

Les conseils sur std::vector::reserve s’appliquent ici.

Je préfère boost::lexical_cast pour mettre en œuvre les implémentations de stream pour des raisons de maintenance, bien que je ne puisse pas dire qu’il soit plus ou moins performant que les implémentations de stream. Je dirai qu’il est extrêmement rare de voir une vérification d’erreur correcte lors de l’utilisation du stream.

Manigances STL.

Je suis intentionnellement vague sur ceux-ci, désolé. J’écris habituellement du code qui évite les conditions, bien que je me souvienne de certains des essais et des sortingbulations dont mes collègues m’ont parlé. Utiliser STLPort en évite une bonne partie.

Sur certaines plates-formes, les opérations de stream ont une sécurité de thread bizarre activée par défaut. J’ai donc vu un usage mineur de std :: cout détruire absolument les performances d’un algorithme. Vous n’avez rien ici, mais si vous vous êtes connecté dans un autre thread, cela pourrait poser problème. Je vois 8% _Mutex::Mutex à 8% _Mutex::Mutex dans un autre commentaire, ce qui peut en dire 8% _Mutex::Mutex sur son existence.

Il est plausible qu’une implémentation STL dégénérée puisse même avoir le problème ci-dessus avec les opérations de stream d’parsing lexicale.

Il existe des caractéristiques de performance étranges sur certains des conteneurs. Je n’ai jamais eu de problèmes avec le vecteur, mais je n’ai vraiment aucune idée de ce que istream_iterator utilise en interne. Par le passé, j’ai suivi, par exemple, un algorithme faisant preuve de mauvaise conduite pour trouver un appel std::list::size effectuant un parcours complet de la liste avec GCC. Je ne sais pas si les versions les plus récentes sont moins naïves.

La stupide stupidité habituelle de SECURE_CRT devrait être bêtement prise en charge. Je me demande si c’est ce que Microsoft pense que nous voulons passer notre temps à faire?

List.Add et vector::push_back List.Add de temps en temps la mémoire au fur et à mesure de la croissance du conteneur. Le vecteur C ++ stocke les sous-vecteurs par valeur. Ainsi, toutes leurs données (qui semblent énormes dans votre cas) sont copiées à plusieurs resockets. En revanche, la liste C # stocke les sous-listes par référence. Par conséquent, les données des sous-listes ne sont pas copiées lors de la réaffectation.

L’implémentation vectorielle typique double sa capacité lors de la réallocation. Donc, si vous avez 1 million de lignes, les sous-vecteurs seront copiés log (2,1000000) 10 fois.

La sémantique de déplacement introduite dans C ++ 11 est supposée éliminer cet effet. En attendant, essayez le vector< shared_ptr< vector > > , la list< vector > ou, si vous connaissez la taille future à l’avance, utilisez vector::reserve() pour éviter les réaffectations.

Le code n’a pas été testé, mais combien d’ ints -ils chargés? Considérez ce qui se passe lorsque chacun de vos vectors atteint sa capacity . Un vector croît de manière inefficace – O (n) je crois. La liste de C # n’a pas ce comportement.

Pensez à utiliser std::deque , std::list ou un autre conteneur qui présente un meilleur comportement de croissance. Voir cet article pour plus d’informations.

Si vous avez un très grand nombre d’éléments, vous serez puni de la réaffectation et de la copie à chaque fois que le vecteur est repoussé. Essayez d’utiliser un autre conteneur en C ++.

Puisque votre fonction elle-même n’est pas lente 1 , la raison pour laquelle le programme est lent doit être qu’un code utilisant le produit de cette fonction devient plus lent une fois que pathLookupVectors a été pathLookupVectors .

Je pense que l’exécution d’un profileur sur votre programme serait le meilleur moyen de le savoir, mais vous pouvez également consulter votre code pour rechercher tous les éléments de code dépendant de pathLookupVectors et déterminer s’il s’agit du goulot d’étranglement que vous recherchez.

1. Établi dans votre dernière édition.