Lors d'un discours Unity sur les performances, le gars nous a dit d'éviter la for each
boucle pour augmenter les performances et d'utiliser à la place la boucle normale. Quelle est la différence entre for
et for each
du point de vue de la mise en œuvre? Qu'est-ce qui rend for each
inefficace?
unity
performance
Emad
la source
la source
for each
boucles itérant sur autre chose qu'un tableau génèrent des ordures (versions Unity antérieures à 5.5)Réponses:
Une boucle foreach crée ostensiblement un objet IEnumerator à partir de la collection que vous lui avez passée, puis passe par-dessus. Donc, une boucle comme celle-ci:
se traduit par quelque chose comme ceci:
(éluder une certaine complexité autour de la façon dont l'énumérateur est éliminé plus tard)
Si cela est compilé naïvement / directement, cet objet énumérateur est alloué sur le tas (ce qui prend un peu de temps), même si son type sous-jacent est un struct - quelque chose appelé boxe. Une fois la boucle terminée, cette allocation devient des ordures à nettoyer plus tard, et votre jeu peut bégayer pendant un moment si suffisamment de déchets s'empilent pour que le collecteur exécute un balayage complet. Enfin, la
MoveNext
méthode et laCurrent
propriété peuvent impliquer plus d'étapes d'appel de fonction que de simplement saisir un élément directement aveccollection[i]
, si le compilateur n'inline pas cette action.Les liens Unity docs Hellium ci - dessus indiquent que cette forme naïve est exactement ce qui se passe dans Unity avant la version 5.5 , si elle itère sur autre chose qu'un tableau natif.
Dans la version 5.6+ (y compris les versions 2017), cette boxe inutile a disparu et vous ne devriez pas rencontrer d'allocation inutile si vous utilisez un type concret (par exemple,
int[]
ouList<GameObject>
ouQueue<T>
).Vous obtiendrez toujours une allocation si la méthode en question sait seulement qu'elle fonctionne avec une sorte d'IList ou une autre interface - dans ces cas, elle ne sait pas avec quel énumérateur spécifique elle travaille, elle doit donc la placer dans un objet IEnumerator en premier. .
Il y a donc de fortes chances que ce soit de vieux conseils qui ne sont pas si importants dans le développement moderne de Unity. L' unité devs comme nous pouvons être un peu superstitieux choses qui utilisé pour être poilu lent / dans les anciennes versions, afin de prendre des conseils de cette forme avec un grain de sel. ;) Le moteur évolue plus vite que nos croyances à ce sujet.
Dans la pratique, je n'ai jamais vu un jeu présenter une lenteur notable ou un hoquet de collecte de déchets à l'aide de boucles foreach, mais votre kilométrage peut varier. Profilez votre jeu sur le matériel que vous avez choisi pour voir si cela vaut la peine de s'inquiéter. Si vous en avez besoin, il est assez trivial de remplacer les boucles foreach par des boucles for explicites plus tard dans le développement si un problème se manifeste, donc je ne sacrifierais pas la lisibilité et la facilité de codage au début du développement.
la source
List<MyCustomType>
etIEnumerator<MyCustomType> getListEnumerator() { }
ça va, alors qu'une méthodeIEnumerator getListEnumerator() { }
ne le serait pas.Si un pour chaque boucle utilisé pour la collection ou un tableau d'objets (c'est-à-dire un tableau de tous les éléments autres que le type de données primitif), GC (Garbage Collector) est appelé pour libérer l'espace de la variable de référence à la fin d'un pour chaque boucle.
Alors que la boucle for est utilisée pour itérer sur les éléments à l'aide d'un index et donc le type de données primitif n'affectera pas les performances par rapport à un type de données non primitif.
la source
temp
variable ici peut être allouée sur la pile, même si la collection est pleine de types de référence (car c'est juste une référence aux entrées existantes déjà sur le tas, pas d'allocation de nouvelle mémoire de tas pour contenir une instance de ce type de référence), donc nous ne nous attendrions pas à ce que des ordures se produisent ici. L'énumérateur lui-même peut être encadré dans certaines circonstances et se retrouver sur le tas, mais ces cas s'appliquent également aux types de données primitifs.