Durante uma palestra do Unity sobre desempenho, o cara nos disse para evitar o for each
loop para aumentar o desempenho e usar o loop for normal. Qual é a diferença entre for
e for each
no ponto de vista da implementação? O que torna for each
ineficiente?
unity
performance
Emad
fonte
fonte
for each
loops que iteram sobre qualquer coisa que não seja uma matriz geram lixo (versões do Unity anteriores à 5.5) #Respostas:
Um loop foreach ostensivamente cria um objeto IEnumerator a partir da coleção que você passa e, em seguida, passa por ele. Então, um loop como este:
traduz para algo mais ou menos assim:
(elimina alguma complexidade sobre como o enumerador é descartado posteriormente)
Se isso for compilado de forma ingênua / direta, esse objeto enumerador será alocado no heap (o que leva um pouco de tempo), mesmo que seu tipo subjacente seja uma estrutura - algo chamado boxe. Após a conclusão do loop, essa alocação se torna lixo para limpeza posterior, e seu jogo pode gaguejar por um momento, se houver lixo suficiente para o coletor executar uma varredura completa. Por fim, o
MoveNext
método e aCurrent
propriedade podem envolver mais etapas de chamada de função do que simplesmente pegar um item diretamentecollection[i]
, se o compilador não incorporar essa ação.Os links Hellium dos documentos do Unity acima indicam que essa forma ingênua é exatamente o que acontece no Unity antes da versão 5.5 , se iterar sobre algo diferente de uma matriz nativa.
Na versão 5.6+ (incluindo as versões de 2017), esse boxe desnecessário desapareceu e você não deve ter alocação desnecessária se estiver usando algum tipo concreto (por exemplo,
int[]
ouList<GameObject>
ouQueue<T>
).Você ainda receberá uma alocação se o método em questão souber apenas que está trabalhando com algum tipo de IList ou outra interface - nesses casos, ele não sabe com qual enumerador específico está trabalhando, portanto, deve colocá-lo em um objeto IEnumerator primeiro .
Portanto, há uma boa chance de que este seja um conselho antigo que não é tão importante no desenvolvimento moderno do Unity. Os desenvolvedores de unidade como nós podem ser um pouco supersticiosos em relação às coisas que costumavam ser lentas / peludas em versões antigas; portanto, aceite qualquer conselho dessa forma com um pouco de sal. ;) O mecanismo evolui mais rapidamente do que nossas crenças sobre ele.
Na prática, eu nunca tive um jogo exibindo lentidão perceptível ou soluços de coleta de lixo usando loops de foreach, mas sua milhagem pode variar. Faça o perfil do seu jogo no hardware escolhido para ver se vale a pena se preocupar. Se você precisar, é bastante trivial substituir loops foreach por loops explícitos mais tarde no desenvolvimento, caso um problema se manifeste, para que eu não sacrifique a legibilidade e a facilidade de codificação no início do desenvolvimento.
fonte
List<MyCustomType>
eIEnumerator<MyCustomType> getListEnumerator() { }
estão bem, enquanto um métodoIEnumerator getListEnumerator() { }
não seria.Se a para cada loop usado para coleta ou matriz de objeto (ou seja, matriz de todos os elementos, exceto o tipo de dados primitivo), o GC (Garbage Collector) é chamado para liberar espaço da variável de referência no final de uma para cada loop.
Enquanto o loop for é usado para iterar sobre os elementos usando um índice e, portanto, o tipo de dados primitivo não afetará o desempenho em comparação com um tipo de dados não primitivo.
fonte
temp
variável aqui pode ser alocada na pilha, mesmo que a coleção esteja cheia de tipos de referência (já que é apenas uma referência às entradas existentes já no heap, não alocando nova memória heap para armazenar uma instância desse tipo de referência), portanto não esperamos que o lixo ocorra aqui. O próprio enumerador pode ser encaixotado em algumas circunstâncias e acabar no heap, mas esses casos também se aplicam a tipos de dados primitivos.