Le grand Dieu Google n'a pas été à venir avec une explication pour des problèmes d'optimisation de la boucle. Donc, dans la tristesse que je suis insuffisant Google-Fu, je me tourne vers vous Stackoverflow.
J'épuisant un programme C pour résoudre un système spécifique d'équations différentielles. Au cours de la recherche de la solution numérique, j'appelle une fonction qui définit un système linéaire d'équations, puis une fonction qui la résout. P>
la fonction de solution a initialement eu un goulot d'étranglement lors de l'accès des éléments sur l'accès des éléments sur le diagonale de la matrice qui définit le système linéaire. J'ai donc inclus un tableau 1-D défini lors de l'initialisation du système qui contient les valeurs le long de la diagonale de la matrice. P>
Pour le plaisir, j'ai continué à jouer avec le code initialisé des éléments diagonaux, mesurer le temps qu'il a fallu et essayant d'améliorer continuellement le code. Les versions que j'ai essayées menées à quelques questions: p>
Déclarations pertinentes de données utilisées dans le code: p> ma boucle d'origine forte> pour initialiser diag_data. Le calendrier était de 16,1% de l'évaluation (voir note). P> memset(diag_data, '\0', sizeof(diag_data));
for (iter = 0; iter<98;) {
diag_mask[iter] = spJ[sp_diag_ind[iter++]];
}
3 Réponses :
Une boucle non alignée est celle où la première instruction ne démarre pas sur une limite particulière (multiple de 16 ou 32). Il devrait y avoir un drapeau du compilateur pour aligner les boucles; Il peut ou non aider la performance. Si une boucle est alignée ou non sans que le drapeau est basé sur exactement quelles instructions arrivent devant elle, donc ce n'est pas prévisible. Une autre optimisation que vous pouvez essayer est de marquer diag_mask code>,
SPJ code> et
sp_diag_ind code> comme
restreindre code> (une fonctionnalité C99) . Cela indique qu'ils n'apparaissent pas d'alias et pourraient aider le compilateur à optimiser la boucle mieux. Un nombre de 98 sera probablement trop petit pour voir n'importe quel effet, cependant. P>
Votre cinquième version est incorrecte - il a comportement non défini em>, car il modifie les deux Avez-vous essayé de stocker les valeurs em> des diagonales, plutôt que de leurs index dans La partie pertinente de la norme C est § 6.5 Expressions: P> '2. Entre le point de séquence précédent et suivant, un objet doit avoir
sa valeur stockée modifiée au plus une fois
par l'évaluation d'une expression.
En outre, la valeur préalable doit être
Lisez seulement pour déterminer la valeur à être
stocké. p>
BlockQuote> Ceci s'applique à l'objet GCC (testé avec la version 4.4.5) prévient même de votre expression: P> iter code> et références sa valeur, à des fins autres que de calculer la nouvelle valeur, sans Point de séquence intermédiaire.
SPJ code>, au point que vous calculez
sp_diag_ind [ ] code>? Ensuite, vous pouvez simplement les copier directement dans
diag_data code> (ou, bien mieux, utilisez le vecteur de diagonales directement). P>
iTER code> dans votre expression. La violation d'une contrainte «doit» est un comportement indéfini. P>
x.c:16: warning: operation on âiterâ may be undefined
Êtes-vous sûr qu'il est indéfini? J'étais sous l'impression que (au moins en C) ++ j'ai utilisé dans une expression incrémenterais, j'utilise ensuite sa valeur et que je ++ dans une expression utiliserait la valeur de i puis de l'incrémenter. En outre, après avoir joué avec quelques versions de ceci, y compris ceux où j'utiliserais différentes combinaisons de +2 ou +3 pour l'ingérée et incrémentation des points à différents endroits de l'expression avant et après l'utiliser (par mes notions ), GCC-4.2 au moins semble respecter mon compréhension.
@Seven, oui, il est indéfini. Le problème est que vous utilisez i code> sur le côté gauche de l'expression d'affectation et
i ++ code> sur le côté droit de l'expression de l'affectation. C ne définit pas l'ordre dans lequel ces deux expressions sont évaluées. Étant donné que le calcul dépend de l'ordre indéfini de l'évaluation des expressions, le résultat n'est pas défini.
@Seven: Il est incrémenté à un moment donné avant le prochain point de séquence. Ainsi, à part quelque chose d'autre, vous ne savez pas si le premier [iter] code> utilisera l'ancienne ou la nouvelle valeur. Mais de côté, toute l'expression est tout simplement indéfinie pour la raison que j'ai donnée - vous ne pouvez pas modifier une variable et accéder à sa valeur pour une raison non liée sans point de séquence intermédiaire. J'ai ajouté la partie pertinente de la norme à ma réponse.
@RLIBBY Merci, je n'avais pas réalisé que l'affectation n'a pas procédé à l'évaluation de l'expression. Heureux d'apprendre ce bit.
La question est modifiée pour marquer le mauvais code en tant que tel. Merci encore.
Voyez-vous d'autres améliorations que je peut faire? p> blockQuote>
Vous réglez la lumière du jour de quelque chose qui utilise environ 11% du temps. N'y a-t-il rien dans les 89% d'autres pouvant être optimisés? P>
Les pourcentages étaient les temps passés dans chaque version que j'ai produite. C'est-à-dire que foo () {version1 () / * prend 16% de FOO * /; Version2 () / * prend 12% de FOO * /; ...}. Une fois que je ne comparais pas aux versions, j'utilise une seule version à l'intérieur de FOO (), mais l'heure à FOO est grande par rapport à d'autres fonctions que je n'ai pas eues. En outre, cette boucle prend environ 85% de FOO () lors de l'utilisation d'une version. Désolé si cela n'était pas clair dans le Q.
@Sevenless: FWIW, j'utilise la méthode de pause aléatoire qui ne donne pas de percents précis, mais à savoir quel code optimiser, nommément déclarations ou sites d'appel de fonction qui apparaissent sur deux échantillons ou plus. Ensuite, je mesure l'amélioration globale par le timing de haut niveau. Lorsque je descends à un point de rendement décroissant, je considère que le programme est proche de Optimal. Je pourrais peut-être vous raser un peu ici et là, avec plus d'effort, mais il y a un point optimal, et je suis probablement proche de cela.
@Sevenless: Je viens de le faire récemment sur un système ODE. Lors de l'utilisation d'un solutionneur RK, il s'agissait d'abord de nombreuses hits dans la fonction dérivée. J'ai trouvé cela fait des choses là-bas qui pourraient être déplacés ou mémotisés. Cela a donné une bonne vitesse, après quelles déclarations dans le solveur RK lui-même prenait un pourcentage plus grand (comme la moitié). Ensuite, allez à -O3 dans le compilateur a fait une différence importante. Je laisse les échantillons me dire où se trouve le plus d'avantages.