Otimizações de Desempenho em Aplicativos com Corona SDK

Artigo Traduzido do Blog Oficial

Boa tarde pessoal. Felizmente como as vendas do livro que traduzi sobre Corona SDK tem sido boas, e os leitores tem pedido novos posts no blog tratando de assuntos não vistos no livro, eis que volto a postar sobre o assunto. Como ando sem tempo para realizar experimentos com a plataforma por conta própria, decidi apenas traduzir este excelente post que foi publicado no blog oficial da Corona Labs, que pode ser acessado em http://www.coronalabs.com/blog/. Ou seja, não é de minha autoria, embora eu mesmo já tenha utilizado algumas dessas técnicas em meus projetos.

Otimizações de Performance

O tutorial de hoje é sobre otimização de performance, um tópico que preocupa todos desenvolvedores. Algumas dessas dicas serão óbvias – outras, nem tanto. Em todo caso, otimização de código tem de ser vista a partir de uma perspectiva de tempo vs. benefício. Se uma certa otimização pode render um ganho de 2% de performance em dispositivos antigos, mas sua implementação requer 50 horas de codificação adicional, então é ilógico fazê-lo. Entretanto, se 10 horas de codificação resultarão em um ganho perceptível entre vários grupos de dispositivos, então a tarefa é absolutamente valiosa.

A respeito de novos projetos, aderir a tantos truques de performance quanto possível é altamente recomendado, uma vez que eles resultam em aplicativos mais velozes e limpos, bem como uma melhor experiência para os usuários de diferentes dispositivos.

Rotinas de Tempo-Crítico

A maioria dos truques de performance apresentadps neste tutorial pertencem primeiramente a rotinas de “tempo-crítico” – ou seja, pontos na sua aplicação onde existe um monte de coisas acontecendo ou onde a experiência do usuário pode ser negativamente afetada por falhas de performance. Por exemplo, o gameplay de um jogo de ação, a espera entre carregar uma nova cena, etc. Se o usuário notar frame skips ou se ele tiver de esperar mais do que considera aceitável, isso refletirá na aplicação.

Curso React Native

1. Localize, Localize

Não importa quantas vezes isso é mencionado, é muito valioso enfatizar novamente. Enquanto evitar variáveis e funções globais nem sempre é possível, o uso mínimo é a melhor prática. O acesso de variáveis locais e funções é simplesmente mais rápido especialmente em rotinas de tempo-crítico.

Não recomendado:

Recomendado:

Isto também se aplica às bibliotecas principais do Lua como a math. Em rotinas de tempo-crítico, você deve sempre localizar as funções da biblioteca.

Não Recomendado:

Recomendado:

E por último, lembre-se que funções sempre devem ser localizadas se possível. É claro, iso irá requerer escopo apropriado. Se você é um novato com Lua, leia este artigo.

Não Recomendado:

Recomendado:

2. Evite Funções como Argumentos para Outras Funções

Em laços ou código de tempo-crítico, é essencial localizar funções que serão parâmetros de outras funções. Examine estes dois casos:

Não Recomendado:

Recomendado:

3. Evite “table.insert()”

Vamos comparar quatro métodos de conseguir a mesma coisa: o ato comum de inserir valores em uma table. Dos quatro, a função Lua table.insert possui a performance mais sofrível e deve ser evitada.

Não Recomendado:

Recomendado:

Aceitável:

Recomendado:

4. Minimize o uso de “unpack()”

A função Lua unpack() não possui uma boa performance. Felizmente, geralmente é possível escrever um simples e mais rápido laço para fazer a mesma coisa.

Não Recomendado:

Recomendado:

O único porém é que você deve saber o comprimento da table para retornar todos seus valores neste tipo de método em loop. Entretanto, unpack() ainda tem seus usos – em uma table de comprimento desconhecido, por exemplo – mas deve ser evitada em rotinas de tempo-crítico.

5. Cache de Acesso a Itens da Table

Fazer cache de itens da table, especialmente dentro de laços, pode turbinar sua performance ligeiramente e pode ser considerada um código de tempo-crítico.

Aceitável:

Recomendado:

6. Evite “ipairs()”

Quando estiver iterando através de uma table, a sobrecarga da função Lua ipairs() não justifica seu uso, especialmente quando você pode conseguir a mesma coisa usando uma construção Lua.

Não Recomendado:

Recomendado:

7. Comparações de Desempenho Matemático

Várias funções e processos matemáticos são mais rápidos que outros e devem ser favorecidos.

Evite “math.fmod()” para Números Positivos:

Multiplicação é mais Veloz que Divisão:

Multiplicação é mais Veloz que Exponenciação:

8. Economize Memória de Texturas

Memória de texturas é muitas vezes ignorada até que chegue em um ponto crítico, quando é difícil fazer as alterações necessárias nas imagens do aplicativo.

A respeito de memória de texturas, imagens PNG de 8bit ou 24bit são todas empacotadas em imagens de 32bit. Elas são vetores retangulares de pixels e existem efetivamente 4 vetores de cores (canais) por imagem: vermelho, verde, azul e alfa (RBG+A).

Em OpenGL, texturas – independente se imagens simples ou folhas de imagens – também obedecem a regra da Potência de 2. Isto significa que qualquer textura arredondará para cima para a próxima potência de 2 (2,4,8,16,32,etc) referente à memória que irá ocupar. Desta forma, uma imagem dimensionada como 320×480 e outra dimensionada para 280×400 irão ambas consumir 512×512 de memória de textura. Note que a regra irá será aplicada na vertical ou horizontal, de forma independente e seu tamanho efetivo nem sempre é um quadrado – portanto, uma imagem dimensionada como 920×40 irá ocupar 1024×64 em memória, e não 1024×1024.

Enquanto isto pode parecer inócuo à primeira vista, vamos calcular o consumo real de memória. Não somente devemos considerar o tamanho com a regra da potência de 2, mas também os 4 canais de cores. Isto significa que cada pixel no vetor da textura requer 4 bytes de memória, e isto cresce mais rápido do que você imagina.

Imagem 350×500: 512x512px * 4 bytes = 1.048.576 bytes = 1MB

Imagem 512×1024: 1024x1024px * 4 bytes = 4.194.304 bytes = 4MB

Note que o arredondamente pela regra da potência de dois requer o quádruplo de memória para textura! Isto torna-se ainda mais preocupante quando você considera o desenvolvimento para dispositivos comuns e dispositivos Retina/HD. Se o tamanho das telas é o dobro uma da outra, como iPad vs iPad Retina, todas suas imagens precisarão do dobro de tamanho para não perderem qualidade. Entretanto, dobrando seu tamanho requer mais do que o dobro de memória de textura – 4x no exemplo acima – e genericamente falando, os dispositivos Retina/HD não possuem 4x mais memória que seus predecessores!

Antes que você entre em pânico, imagine que a memória de textura pode geralmente ser gerenciada sem uma quantidade excessiva de esforço ou (aham!) retrabalho em um grupo de imagens. Lembre-se apenas destas dicas:

1. Sempre descarregue texturas (remova-as da cena) quando não são mais necessárias.

2. Se você tem uma textura de background que precise ser 525×600 na tela, voc6e pode estar apto a criar uma imagem 448×512 para restringi-la ao intervalo 512 da regra da potência de dois. Então, no código, escale-a levemente definindo a largura e altura desejadas. Se for somente um pequeno aumento no tamanho, a minúscula perda de qualidade não será percebida pelos usuários, principalmente em dispositivos pequenos.

3. Reuse texturas se possível, e aplique cor com a API setFillColor(). Por exemplo, se você tem uma maçã vermelha e uma verde, voc6e pode criar uma maçã em escala de cinza e aplicar as cores vermelha e verde respectivamente.

4. Se você está usando folhas de imagens, considere usar uma ferramenta como Texturepacker para empacotar suas imagens na menor configuração da potência de dois possível.

9. Pré-crie Corpos Físicos

Se você pretende usar um número considerável de corpos físicos em seu cenário, pode ser sábio pré-criá-los em uma área de código que não seja de tempo crítico. Aqueles que não serão usados imediatamente podem ser definidos como inativos e colocados em algum lugar fora da tela ou em um grupo invisível, retornando ao estado ativo quando necessário.

Como tem sido dito, criar alguns corpos físicos durante um código de tempo-crítico não traz problemas – apenas evite criar entre 10 a 20 corpos físicos durante um ciclo do jogo, o que frequentemente refletirá em queda de frame rate.

Além disso, deve se usar esta técnica de maneira balanceada. Pré-criar e desativar 200 corpos físicos removerão eles do mundo do Box2D, mas não da memória do Corona, então não leve esta prática ao extremo.

10. Utilize as Melhores Práticas para Áudio

Efeitos sonoros para um aplicativo devem sempre ser pré-carregados em codigo não crítico, por exemplo, antes da cena ou fase começar. Adicionalmente, voc6e deve comprimir os sons para a qualidade aceitável com o menor tamanho na maioria dos casos. 11khz mono é considerada aceitável na maioria dos casos, como o usuário geralmente estará escutando através dos alto-falantes ou microfones do celular. Além disso, usando formatos simples multi-plataforma como WAV você economizará CPU.

Se desejado, efeitos sonoros podem ser organizados em uma table, para fácil referência e eventual descarregamento quando não forem mais necessários:

Com esta estrutura, playback é simples como:

Como sempre, não esqueça de limpar seus sons invocando dispose() neles quando não são mais necessários e limpando a referência da table:

Isto é tudo por hoje. Como um desenvolvedor, otimização de performance requer diligência, e você deve sempre aderir às melhores práticas. Espero que essas dicas forneçam conhecimento necessário para turbinar a performance de seus aplicativos. Como sempre, contribua com seus questionamentos e comentários abaixo.

* OBS: curtiu o post? Então dá uma olhada no meu livro de Corona SDK clicando no banner abaixo pra aprender a criar outros tantos apps incríveis!

Livro Corona SDK
Livro Corona SDK

Publicado por

Luiz Duarte

Pós-graduado em computação, professor, empreendedor, autor, Agile Coach e programador nas horas vagas.