7
votes

Est-il préférable de déclarer un local à l'intérieur ou à l'extérieur d'une boucle?

Je suis habitué à faire ceci: xxx

au lieu de xxx

mon raisonnement est que la création d'une variable locale 1000000 fois est moins efficace que la création juste une fois et la réutiliser sur chaque itération.

Ma question est la suivante: est-ce vrai ou il y a un autre détail technique que je manque? Je demande parce que je ne vois personne de le faire, mais je ne sais pas si la raison est parce que l'avantage est trop petit ou parce que c'est en fait pire. Pour mieux, je veux dire en utilisant moins de mémoire et de courir plus vite.


1 commentaires

"Créer une variable locale" est quelque chose vous faire lorsque vous écrivez le code source. Ce qui se passe au runtime est, je suppose, aussi inconnu de vous que pour moi. (D'accord, j'ai joué avec luac -l et lisez un peu l'instruction VM.)


4 Réponses :


2
votes

Notez d'abord ceci: Définition de la variable à l'intérieur de la boucle S'assure qu'après une itération de cette boucle, la prochaine itération ne peut plus utiliser la même variable stockée. Définir avant que la boucle pour la boucle permet de transporter une variable via plusieurs itérations, comme toute autre variable non définie dans la boucle.

En outre, pour répondre à votre question: Oui, il est moins efficace, car il réintroduit la variable. Si le compilateur Lua a une bonne reconnaissance de modèle, il se peut que cela réinitialise simplement la variable, mais je ne peux pas confirmer ni nier cela.


2 commentaires

Désolé je suis confus. Lequel faites-vous référence lorsque vous dites "oui, il est moins efficace"? Dans la fois la valeur variable, on redéfinie chaque itération.


Désolé pour la confusion. Je mentionnais la déclaration de variables en boucle lors de la rédaction de «Oui, il est moins efficace».



11
votes

Comme toute question de performance, mesurez-la en premier. Dans un système UNIX, vous pouvez utiliser le temps: xxx

la sortie: xxx

La version la plus locale semble être un petit pourcentage plus rapide dans Lua, puisque elle n'initialise pas a à nil. Cependant, ce n'est pas une raison pour l'utiliser, Utilisez la portée la plus locale car elle est plus lisible (c'est bon style dans toutes les langues: voir cette question posée pour C , Java , et C # )

Si vous réutilisez une table au lieu de la créer dans la boucle, il y a probablement une différence de performance plus significative. En tout état de cause, mesurer et favoriser la lisibilité chaque fois que vous le pouvez.


8 commentaires

Tache sur. La lisibilité est 100 fois plus importante que insignifiant performance Tweaks.


Run! Faites premier && secondes dans la coquille quelques fois et vous verrez qu'il n'y a pas de strict-plus rapide. ** 1253 ** / 1022, 1020/1022, 1023/1019, 102222/1021, 1022/1022, 1028/1019, 1021/1021, 1021/1022, ...


En fait, il existe dans les deux cas deux emplacements prédéfinis dans l'enregistrement d'activation et il n'y a aucune différence dans les opcodes.


@ user3125367 Les opcodes ne diffèrent que par un loadnil dans le premier depuis depuis que a est par défaut initialisé sur nil . Le point est que la différence de performance est insignifiante et la seconde est meilleure pour la lisibilité.


Vous pouvez le voir dans ma réponse maintenant (et vérifier vous-même).


@ user3125367 En réalité, il est luac 5.2.3 génère le loadnil


Comment mesurer cela sur le système Windows?


@EdwardBlack (je n'ai pas essayé ceci) Windows équivalent à la commande Time Unix .



6
votes

Je pense qu'il y a une certaine confusion sur la façon dont les compilateurs traitent des variables. D'un type de perspective humain de haut niveau, il est naturel de penser à définir et à détruire une variable pour avoir une sorte de "coût" associé à celui-ci.

Ce n'est pas nécessairement le cas pour optimiser le compilateur. Les variables que vous créez dans une langue de haut niveau sont plus comme des "poignées" temporaires en mémoire. Le compilateur regarde ces variables, puis la traduit par une représentation intermédiaire (quelque chose de plus proche de la machine) et des chiffres sur l'endroit où stocker tout, principalement dans le but d'attribuer des registres (la forme la plus immédiate de mémoire pour la CPU à utiliser). Ensuite, il traduit l'IR en code machine où l'idée d'une "variable" n'existe même pas, mais uniquement des endroits pour stocker des données (registres, cache, dram, disque).

Ce processus comprend la réutilisation de la même manière Registres pour plusieurs variables à condition qu'ils n'interfèrent pas les uns avec les autres (à condition qu'ils ne soient pas nécessaires simultanément: ne pas "vivre" en même temps).

Mettez un autre moyen, avec code comme: xxx

L'assembly résultant pourrait être quelque chose comme: xxx

... ou peut déjà avoir le résultat d'une expression dans un Inscrivez-vous et la variable finit par disparaître complètement (simplement en utilisant ce même registre pour cela).

... ce qui signifie qu'il n'y a pas de "coût" à l'existence de la variable. Cela se traduit directement vers un registre qui est toujours disponible. Il n'y a pas de "coût" de "créer un registre", car les registres sont toujours là.

Lorsque vous commencez à créer des variables dans une portée plus large (moins locale), contrairement à ce que vous pensez, vous pouvez en réalité Ralentissez le code. Lorsque vous faites cela superficiellement, vous vous battez en quelque sorte contre l'allocation de registre du compilateur et que le compilateur signifie que le compilateur découvre ce que les registres d'allouer pour quoi. Dans ce cas, le compilateur peut renverser plus de variables dans la pile qui est moins efficace et a réellement un coût attaché. Un compilateur intelligent peut toujours émettre du code équitablement efficace, mais vous pourriez réellement rendre les choses plus lentes . Aider le compilateur ici signifie souvent plus de variables locales utilisées dans de petites étendues où vous avez la meilleure chance d'efficacité.

dans le code de montage, réutilisez les mêmes registres chaque fois que vous pouvez être efficace pour éviter les déversements de pile. Dans les langues de haut niveau avec des variables, c'est un peu le contraire. Réduire la portée des variables aide le compilateur Soignez les registres de la réutilisation, car l'utilisation d'une portée plus locale pour les variables permet d'informer le compilateur que les variables ne sont pas en ligne simultanément.

maintenant Il existe des exceptions lorsque vous commencez à impliquer le constructeur et la logique destructeurs définis par l'utilisateur dans des langues telles que C ++, où la réutilisation d'un objet est pourrait empêcher la construction et la destruction redondantes d'un objet pouvant être réutilisé. Mais cela ne s'applique pas dans une langue comme Lua, où toutes les variables sont fondamentalement des données anciennes simples (ou des poignées dans des données collectées par des ordures ou UserData).

Le seul cas où vous pourriez voir une amélioration en utilisant moins Les variables locales sont si cela réduit en quelque sorte le travail pour le collecteur des ordures. Mais cela ne sera pas le cas si vous revenez simplement à la même variable. Pour ce faire, vous devriez réutiliser des tables entières ou des données utilisateur (sans ré-attribution). Mettez un autre moyen, réutilisant les mêmes champs d'une table sans recréer une nouvelle nouvelle pourrait aider dans certains cas, mais la réutilisation de la variable utilisée pour référencer le tableau est très peu susceptible d'aider et pourrait réellement entraver la performance. >


1 commentaires

En effet. Quand je regarde local A; Pour i = 1 100000000, do a = i * 3 fin Je peux voir que cela ne fait rien du tout. Je sais que les écrivains compilateurs sont plus intelligents que je ne serais donc pas surpris si un compilateur C ++ optimise quelque chose comme ça dans un Nop.



3
votes

Toutes les variables locales sont "créées" à la compilée ( charger ) et sont simplement indexés dans le bloc local de l'enregistrement d'activation de la fonction. Chaque fois que vous définissez un local , ce bloc est cultivé par 1. Chaque fois que ..endez / bloc lexical est terminé, il rétrécit. La valeur maximale est utilisée comme taille totale: xxx

La fonction ci-dessus contient 3 emplacements locaux (déterminé à charger , pas au moment de l'exécution).

En ce qui concerne votre cas, il n'y a pas de différence dans la taille des blocs locaux, et de plus, luac /5.1 génère des listes égales (uniquement des index change): xxx

vs xxx

// [n] -Comments sont la mine.


0 commentaires