12
votes

Netté pour les boucles: lisibilité et performance

Je comprends bien imbriqués pour des boucles. Je comprends ce qu'ils font et comment ils le font. Mais mon problème est qu'ils semblent horriblement illisibles pour moi.

Prenez cet exemple: P>

for (int i = 0, y = 0; y <= ySize; y++) {
    for (int x = 0; x <= xSize; x++, i++) {
        vertices[i] = new Vector3(x, y);
    }
}


3 commentaires

Je crois que ces questions sont plus comme programmeurs.stackexchange.com


@Alex lors de la référence à d'autres sites, il est souvent utile de pointer que Cross-posting est froncé sur


En ce qui concerne la performance, le corps de la boucle la plus interne est généralement passé. Cette boucle ne prend généralement plus de temps par itération simplement parce qu'elle est au bas d'une pile de boucles imbriquées. Ce n'est qu'un problème de performance s'il fait plus d'itérations qu'il n'a besoin. En ce qui concerne la lisibilité, la structure de votre problème détermine la profondeur de votre nidification. Vous pouvez les diviser en fonctions séparées avec des noms descriptifs, pour vous aider à garder une piste.


6 Réponses :


5
votes

a) généralement, vous constaterez que vous n'aurez pas besoin d'imbrication très profonde, ce ne sera donc pas un problème.

B) Vous pouvez faire les boucles imbriquées dans une méthode distincte. (c.-à-d. Si vous nichez d dans C in b in a - vous pouvez faire un méthode qui accepte a et b comme paramètres et que c et d . Vous pouvez même laisser vs faire ceci pour vous en sélectionnant la C boucle et en cliquant sur EDIT-> Refactor-> Extrait.)

comme pour la performance - évidemment plus de nidification signifie plus d'itérations, mais si vous les avez - vous en avez besoin. Il suffit de changer une boucle imbriquée pour être incluse dans la boucle d'origine (et de calculer où vous êtes à l'intérieur du code "réel") ira d'humaniser habituellement de manière à aucune manière perceptible.


1 commentaires

Merci pour le conseil de la méthode d'extrait. Je n'y ai pas pensé dans ce contexte.



9
votes
var vertices =
 (from y in Enumerable.Range(0, ySize)
  from x in Enumerable.Range(0, xSize)
  select new Vector3(x, y)).ToList();
Loops are overused. Most loops can be expressed as queries. That makes them easier to write and maintain and it makes them expressions as opposed to statements which are easier to move around.Performance is much worse, like 3-10x here. Whether that matters to your specific case depends on how much time is spent here and what your performance goals are.

8 commentaires

Et bien, si nous ne voulons pas être ce niveau élevé. Bien sûr, c'est une bonne alternative, Linq. Mais tout le monde n'est pas obligé de le faire, et si la performance est pire, ce n'est plus une bonne alternative.


Vous impliquez que toutes les requêtes de Linq ne sont pas bonnes car elles sont à peu près toutes lentement qu'une boucle manuelle. Ça ne peut pas avoir raison ...


Pouvez-vous sauvegarder l'affirmation selon laquelle la performance est pire 3-10X?


@USR Bien utiliser Linq requêtes par rapport à des boucles manuelles est quelque chose à équilibrer entre la performance et la lisibilité. Et croyez-moi, la performance peut en réalité être une question dans certains cas, plus que dans d'autres. Une boucle qui fonctionne une fois va bien. Une boucle qui transforme le calcul du 1s en un calcul de 1 m n'est pas bonne.


Et puis vous devez écrire en C sans le luxe de Linq


@Maarten c'est mon expérience avec de telles boucles d'un certain nombre de tests de perf. Linq est beaucoup plus lent qu'une boucle serrée. Par exemple, chaque itération externe attribue un nouveau énumérable.Range (0, XSIZE) . C'est cher. Chaque article traverse je pense que 6 vcalls.


@USR - Pour atténuer cela, vous pouvez stocker la valeur renvoyée à partir de énumérable.Range (0, xsize) dans une variable distincte, puis utilisez-le dans la déclaration de .


@Richardeverett aurait besoin de matérialiser cela dans une liste. Cela fonctionnerait mais il détruirait la nature de l'expression parce qu'elle nécessite une déclaration.



12
votes

La solution habituelle consiste à refroidir dans des méthodes contenant une ou deux pour les boucles et conserve refactoring jusqu'à ce que chaque méthode soit claire et pas trop grande.

Une autre solution pour arrêter l'indentation et séparer la boucle-résultant Les données de la logique de candidature, consiste à utiliser linq. xxx

ceci est uniquement si le tableau sommet est déjà déclaré. Si vous pouvez simplement créer un nouveau tableau, vous pouvez simplement faire simplement ceci: xxx


6 commentaires

Vous pouvez supprimer l'effet secondaire méchant i ++ de la requête en utilisant le Sélectionnez ((élément, index) => ...) surcharge. Cela crée les index pour vous.


@usr qui ne fonctionnera pas depuis que le i continuera d'augmenter pour une nouvelle valeur de y .


@USR Il peut bien sûr être refacturé dans i = ((Y-1) * ysize) + x); mais je n'ai pas voulu modifier la logique qui est dans la question.


Ce que je voulais dire était coords.select ((élément, index) => ...) . Ajoutez l'index après calcul des valeurs x, y.


Vous avez mentionné refactoring et vous avez mentionné Linq. Je suis (presque) tenté de créer un autre compte juste pour pouvoir vous donner les deux avotes que vous méritez!


L'approche LINQ est comme 7x plus lente ici et elle est discutable s'il est plus facile de lire.



23
votes

Je pense que le problème que vous avez ici est moins le imbriqué pour des boucles , et plus une utilisation inhabituelle de variables dans les boucles.

nouvelles lignes avant que les bretelles d'ouverture puissent également aider à la lisibilité ( Bien que ce soit subjectif ).

comment de cela à la place: xxx

Cette approche doit rester relativement lisible pour des dimensions supplémentaires également (dans cet exemple, j'ai déplacé l'incrémentation de i sur sa propre ligne, comme suggéré par usr ). xxx

concernant la performance, je suggérerais de cibler la concentration S'assurer que le code est lisible et compréhensible par un être humain, puis mesurez la performance de l'exécution, éventuellement avec un outil tel que redgate des fourmis


9 commentaires

Je voudrais même tirer le i ++ hors de l'assignation. C'est comme si quelqu'un essayait de compresser le code en essayant de sauver les octets.


D'accord. Ont mis à jour le deuxième exemple pour illustrer cela.


Il convient de mentionner que la mise en place de l'ouverture Brace bouclée sur la même ligne que le blocage est uniquement lisible dans JavaScript - et personne ne sait pourquoi.


@Grimmythopiner - pas tout à fait vrai; Avoir l'assassinement bouclé ouvrant sur la même ligne que le bloc avant qu'il soit connu sous le nom de K & R style et a été utilisé dans Le langage de programmation C , d'où sa popularité. Le style que j'ai suggéré (et préférez) est apparemment connu sous le nom de Allman Style .


@GRIMMTHEOPINER de ne pas être "uniquement lisible dans JavaScript" et je ne connais vraiment personne qui l'a réclamé, JS a une incitation supplémentaire à avoir le bras ouvert à la même ligne - il empêche d'horribles bugs si vous essayez de revenir Un objet - en raison de l'insertion automatique semicolumn (ASI) si vous avez une déclaration de retour et une nouvelle ligne en suivant, alors cela revient immédiatement, ce qui rend le reste du code inaccessible. . Ainsi, si vous pensiez que vous retourniez l'objet littéral sur la ligne suivante ... vous n'êtes pas. Si vous placez l'attelle bouclée sur la même ligne, cela est évité.


@Vld Peut-être que j'aurais dû porter moins d'un visage droit tout en disant cela. :-) Voilà. Mais tout le monde avec lequel je travaille - tout en utilisant C # et Javascipt - a signalé le même phénomène; Normes stylistiques distinctes dans JavaScript, regardez horrible en C #, et inversement. En dépit d'être consulté dans le même rédacteur en chef. (Et personne ne sait pourquoi!)


Le style est clairement un cas d'opicole et de fermeture.


(int b = 0; b <= xsize; b ++) qui ressemble à un bug


Merci @Miketheliar - corrigé.



1
votes

Un nid de boucle N-profond est susceptible d'être plus lisible que toute alternative exprimée en C #. Au lieu de cela, considérons l'utilisation d'une langue de niveau supérieur qui a vecteur d'arithmétique comme une primitive: par exemple, dans numpy L'équivalent direct de votre code est xxx

et la généralisation n-dimensionnelle est xxx


0 commentaires

0
votes

Je viens de jouer ici, mais que diriez-vous de rembourrer les espaces pour que les conditions de boucle soient alignées? Voici le code de @Richard Everett reformaté un peu:

int i = 0;
for             (int y = 0; y <= ySize; y++) {
    for         (int x = 0; x <= xSize; x++) {
        for     (int a = 0; a <= aSize; a++) {
            for (int b = 0; b <= bSize; b++) {
                vertices[i++] = new Vector3(x, y, a, b);
            }
        }
    }
}


0 commentaires