Mitos da otimização de código
Performance virou uma espécie de buzzword nos últimos tempos, onde muita gente gosta de falar sobre “truques” para deixar o código mais performático. Porém a maioria desses “truques” são completamente inúteis (não melhoram a performance) ou fazem uma diferença desprezível para a performance geral do software enquanto haveriam outras coisas que o desenvolvedor poderia fazer que realmente fariam uma diferença significativa na performance geral.
Este artigo foi escrito para quem não se interessa por truques, mandingas ou superstições e está interessado em realmente aprender como escrever softwares mais performáticos.
Mitos
Primeiramente acredito que seja importante falar sobre os erros que as pessoas aprendem sobre otimização de código, pois existe uma chance de que “sua xícara” esteja cheia de informações erradas que irão prejudicar o aprendizado sobre o assunto. Esvazie sua xícara primeiro.
O compilador já otimiza para mim
Embora seja verdade que compiladores modernos contém uma série de algoritmos para otimização de código e que, na gigantesca maioria das vezes, ele faz um bom trabalho, é falsa a ideia de que se o compilador já otimiza o código para você, então você não precisa se preocupar com a performance do código.
Acreditar nisso é equivalente a acreditar que você não precisa mais tomar banho porque a máquina de lavar chique que você comprou na semana passada já lava e seca as roupas para você.
O compilador realmente otimiza código, mas as otimizações que o compilador faz não são as mesmas otimizações que um programador faz manualmente. Então um não substitui o outro, os dois são igualmente necessários para a performance geral do programa.
Faça X e o código ficará mais rápido
Isso vale para qualquer afirmação que você tenha ouvido falar neste formato, como:
- «Use if..else no lugar de switch..case e o código ficará mais rápido»
- «Use for no lugar de recursão e o código ficará mais rápido»
- «Use ponteiro ao invés de array e o código ficará mais rápido»
- «Evite usar if e o código ficará mais rápido»
- etc.
Estas afirmações não estão necessariamente erradas, mas o problema de acreditar nelas como se fosse uma verdade absoluta é que elas carregam um gigantesco DEPENDE implícito. O mito dessas superstições de “faça X e ficará mais rápido” provavelmente surgiu de algum desenvolvedor que sabia o que estava fazendo, fez uma alteração X no código para otimizar a performance e outro desenvolvedor viu aquilo e achou que se tratava de uma regra geral, uma espécie de magia para otimizar código. “O segredo para escrever código mais rápido que a NASA não quer que você saiba”, ou algo do tipo.
Então não, não adianta aplicar uma superstição no seu código achando que ele ficará mais rápido assim. Já vi vários casos de desenvolvedores que pioraram a performance do seu código enquanto aplicavam uma superstição para deixá-lo mais rápido.
Assembly é mais rápido
As pessoas acreditam muito no mito de que escrever código diretamente em assembly cria código mais performático do que código escrito em uma linguagem de alto nível como C. Mas escrever assembly não faz com que magicamente o código fique mais rápido. Isso depende exclusivamente da sua capacidade de escrever código mais performático do que o código assembly que o compilador gera. A gigantesca maioria dos desenvolvedores não conseguem fazer isso.
C é mais rápido
Muita gente até hoje ainda acredita no mito de que “C é a linguagem mais rápida” (ou algo parecido com isso) e por isso acham que seus programas serão “rápidos” só porque estão programando em C. Não é bem por aí, a linguagem não vai fazer milagre.
É o mesmo caso de escrever código em assembly, isso depende exclusivamente da sua capacidade de escrever um código performático em C. Não fique achando que só porque você está programando em C magicamente o programa tem boa performance. Nem ache que um programa escrito em outra linguagem seja necessariamente mais lento.
Falar de performance de uma linguagem de programação por si só já é uma coisa sem sentido, pois não tem lógica dizer que uma linguagem é mais rápida do que outra. Mas esse assunto é longo demais e não faz parte do escopo deste artigo explicar porque falar de “performance de linguagem” está tecnicamente errado.
Só complexidade de algoritmos importa
«…o resto é micro-otimização.»
Falso. Embora analisar a complexidade de algoritmos e se preocupar em escrever algoritmos mais eficientes de fato faça uma grande diferença na performance de um programa, é falsa a ideia de que isto é a única coisa que impacta significativamente a performance.
Este mito está intrinsecamente ligado ao mito de que os compiladores já otimizam o código e por isso “os programadores não precisam se preocupar em otimizar”. Pois a pessoa que acredita nesse mito acha que o programador só precisa se preocupar em escrever algoritmos mais eficientes e o compilador cuida do resto.
Performance não importa no começo
Falso. Muita gente acredita que no início do desenvolvimento de um software a performance não importa, que tudo bem escrever o código “de qualquer jeito” pois a performance só vai ser importante depois de meses ou anos do lançamento do software.
Embora a afirmação não esteja totalmente errada, é um grande erro acreditar que você só precisa se preocupar com a performance do software “depois”. Existem várias preocupações com a performance do sistema que precisam (reforço: precisam, é necessário!) serem levadas em consideração desde o começo do desenvolvimento e até mesmo antes disso (na fase de planejamento/design).
Embora otimização de códigos específicos (como uma função) possam de fato serem deixados para depois, decisões que envolvem a arquitetura do software precisam levar em consideração a performance desde o começo. Pois se não levarem em consideração, não é um problema que pode ser corrigido “depois”. Você teria que mudar a arquitetura do software, o que não pode ser resolvido simplesmente otimizando “aqui” ou “ali” ou refatorando “isso” ou “aquilo”.
Ou seja, performance não diz respeito somente a pedaços de códigos (como funções) mas também diz respeito ao software como um todo. As questões de performance que dizem respeito ao software como um todo não podem ser deixadas para depois e devem ser pensadas desde o começo.
Comparação de hello worlds ou loops
(ou outros pedaços de código que não fazem nada de verdade)
É mito achar que comparar “hello worlds” entre várias linguagens de programação diz alguma coisa em relação a “qualidade” da linguagem. Isso vale também para quando comparam medições de performance com pequenos loops escritos em várias linguagens diferentes e criam um ranque inútil de “linguagens mais rápidas”.
Não é assim que se mede a performance de um software, isso é erro de principiante.
Comparar o tamanho de softwares “Hello Worlds” em várias linguagens diferentes não diz absolutamente nada sobre a qualidade da linguagem de programação ou do compilador. Isso só diz o tamanho das bibliotecas estáticas que foram linkadas ao executável.
Comparar o benchmark de código com loops feito especificamente para fazer o benchmark usando várias linguagens diferentes não diz nada sobre a qualidade da linguagem de programação ou do compilador. Benchmark de verdade deve ser feito com código de verdade, não com código feito para rodar no benchmark só para fazer um rank sem lógica de linguagens mais rápidas.
E um aviso importante: A performance de um trecho de código específico (como uma função) não reflete a performance de um sistema real como um todo. Ledo engano pensar que só porque um código X é mais rápido compilado com a linguagem A do que com a linguagem B, isso significa que sistemas escritos em A são mais rápidos do que sistemas escritos em B.
Performance de software é muito mais complexo do que isso. Comparar a performance de pequenos trechos de código nunca, em hipótese alguma, é uma boa medição para determinar a performance de um sistema completo.
Entendendo performance
Agora (eu espero) os mitos mencionados acima já foram entendidos como erros e que nada daquilo é de fato “entender de performance”, “ser programador raiz” ou qualquer coisa parecida.
Para ser capaz de escrever softwares performáticos é necessário, antes de mais nada, entender o que é performance: A performance de um software é basicamente o quão bem ele usa os recursos do hardware. Quanto melhor um software se aproveita dos recursos do hardware, quanto menor é o “desperdício” desses recursos, mais performático é o software.
Esses recursos podem ser vários, mas os dois principais são: Memória (quanta RAM o seu software usa) e CPU (quanto processamento da CPU o seu software consome).
Tendo isto em mente, fica claro que é necessário entender como, quando e o quanto de recursos do hardware o seu código está usando. Sem ter esta visão, tentar escrever código performático é uma tarefa inútil.
Mas além do “óbvio” acima, também é importante entender quando o seu software fica ocioso, isto é, “parado” esperando alguma coisa acontecer. Pois o tempo ocioso afeta o tempo de execução geral do software. Identificar quando essas “paradas” ocorrem e como evitá-las/diminuir o tempo de espera, também é uma habilidade necessária para ser capaz de escrever softwares que terão menor tempo de execução no geral.
Otimização de código
Otimizar código é o ato de escrever código mais performático. Como você pode reparar “mais performático” requer um ponto de referência, pois é relativo. Não dá para otimizar um código que não existe, é uma afirmação que nem faz sentido. Então tire da cabeça que você irá escrever um código “otimizado” logo na primeira versão do código, pois não tem lógica pensar assim.
Logo, é importante compreender que otimizar código não é uma tarefa primária no desenvolvimento de software. Primeiro você escreve o código e depois, com a primeira versão do código já escrita, você otimiza ele.
Obs.: Isso não significa que performance deve ser ignorada no início do desenvolvimento do software. Como já foi corrigido em um dos mitos mencionados neste artigo, pensar assim é um erro.
Entendido isto, você não pode dizer que “otimizou” um código sem ter evidências de que realmente o novo código é mais performático do que o código anterior. É importante colocar isso na cabeça para não cair em superstições e achar que está escrevendo código “otimizado” quando não está. Tendo as evidências, não há o que se discutir. Não precisa depender de “opinião”, é um fato.
E para obter essas evidências se faz benchmark tanto do código antigo quanto do novo.
Benchmark
Fazer benchmark é medir o consumo de recursos (*geralmente só RAM e CPU) de um determinado trecho de código (como uma função, por exemplo). Tendo a medição, você consegue comparar a performance de duas versões do código e dizer qual delas é mais performática.
Não cabe ao escopo do artigo explicar como fazer benchmark, mas que fique avisado que a maioria das pessoas que eu vejo tentando fazer benchmark fazem do jeito errado. Portanto prefira usar soluções prontas para fazer benchmark caso não saiba o que está fazendo. Há grandes chances de já existir ferramentas/bibliotecas de benchmark nas linguagens que você usa.
*Obs.: Exceto em trechos de código que consumam outros recursos de hardware no qual você está interessado em otimizar, como: VRAM, GPU, I/O, rede etc.
Profiling
Profiling é essencialmente o mapeamento do consumo de recursos ao longo da execução de um software. Geralmente ferramentas de profiling medem o tempo de execução de funções, uso de memória e outros detalhes que são úteis para mapear quais partes do código são mais críticas para a performance do programa e quais são mais lentas e/ou consomem mais memória.
Provavelmente já existem ferramentas para profiling nas linguagens que você usa, então use-as para ter dados e fazer otimização de código onde realmente faz diferença para a performance como um todo do software.
É comum que desenvolvedores joguem tempo fora tentando fazer otimizações de códigos que não mudam em absolutamente nada a performance do software. Muito mais comum do que você está pensando.
Fazer profiling é uma maneira de ter evidências de que trechos X ou Y do software precisam de otimização e que esta otimização irá realmente fazer diferença na performance do software.
O que REALMENTE faz diferença na performance
Até então você já compreendeu erros comuns que as pessoas cometem quando o assunto é performance e já compreendeu o que é performance, o que se deve fazer para otimizar código e como obter dados para fazer otimizações que realmente fazem a diferença na performance de um software.
Mas ainda não sabe como de fato, na prática, você pode fazer softwares mais performáticos. Esta seção tem o intuito de dar dicas e explicações do que você pode fazer nos seus softwares que realmente impactam positivamente a performance de um software, sem superstições ou mandingas. E agora que você já entende sobre como ter evidências de que a performance de um software realmente melhorou (ou não), então você não precisa nem deve “acreditar” em mim. Faça o teste, meça e veja se é verdade ou não que fazer essas coisas realmente melhoram a performance do código.
CONTINUA...
Este artigo é muito grande e o seu conteúdo completo não cabe aqui em uma postagem do TabNews. Para ler a continuação do artigo e aprender como escrever códigos mais performáticos, você pode:
- Ler no Medium: https://blog.freedev.com.br/mitos-da-otimiza%C3%A7%C3%A3o-de-c%C3%B3digo-31f1b310b0f4
- Ler em PDF: https://drive.google.com/file/d/1bHqP_8zWNDP62HRVw49nYqdsxsnPoZya/view
Escolha o que achar melhor.