Olá! Meu nome é Miller Biazus e serei o instrutor deste curso de desempenho e otimização de aplicações.
Audiodescrição: Miller é um homem branco, com cabelo castanho curto e barba. Ele veste uma camisa azul e está em um ambiente de escritório com uma estante ao fundo.
O curso é bastante voltado à linguagem Python, mas nada impede que utilizemos as ferramentas e os conceitos aprendidos aqui na linguagem de programação de nossa preferência.
Neste curso, nós vamos aprender desde os fundamentos de desempenho até as principais métricas, como latência e tempo de CPU. Vamos entender quando é necessário otimizar nosso código, como realizar profiling (análise de desempenho) e identificar os principais gargalos do nosso código. Também abordaremos conceitos como micro e macro otimização e complexidade. Analisaremos todas as principais ferramentas que podemos utilizar para realizar um refactor (refatoração), tornando nosso código mais rápido, eficiente e escalável.
Este é o nosso primeiro curso como instrutores da Alura, e vamos nos esforçar para torná-lo o mais didático possível, para que todos consigam aplicar o conhecimento adquirido no dia a dia. Temos experiência em desenvolvimento Python há aproximadamente 12 anos, com maior foco em desenvolvimento web. Nossa formação inclui bacharelado e mestrado em Ciência da Computação pela Universidade Federal do Rio Grande do Sul. Esperamos que o curso seja prazeroso, que todos aproveitem bastante e consigam aplicar os conceitos aprendidos no cotidiano.
Nos vemos nas aulas!
Na aula de hoje, vamos fazer uma introdução sobre os principais conceitos de desempenho. Analisaremos quais são os principais pilares de desempenho e discutiremos como cada um deles interfere no desenvolvimento de nossas aplicações. O curso é bastante voltado para demonstrações práticas, portanto, esperem algumas amostras de código e, eventualmente, código ao vivo também. Faremos isso juntos. Além disso, teremos algumas atividades extras para desenvolvermos em casa, praticando o conhecimento adquirido durante as aulas do curso.
O que é desempenho? Desempenho é um termo muito amplo que engloba alguns conceitos principais: eficiência, velocidade, escalabilidade e otimização. Cada um deles representa uma porção do que pode ser otimizado e define como nosso código se comporta em relação aos inputs do usuário, ao tamanho da entrada e ao tempo de resposta demonstrado para o usuário. Vamos analisar cada um deles para fundamentar melhor nosso curso.
O que é eficiência? Eficiência é tentar reproduzir nosso código com o menor número de recursos possível, utilizando de forma coesa os recursos, sem exceder a quantidade de memória necessária para executar uma certa instrução ou bloco de código. Por exemplo, temos um código introdutório no qual geramos uma lista e somamos os valores dessa lista. No primeiro caso, geramos a lista em memória, o que tem um custo de armazenamento muito grande, consumindo muita memória. No segundo caso, geramos a lista sob demanda, sem precisar alocar uma quantidade gigante de memória, utilizando a CPU de forma mais eficiente e evitando armazenamento desnecessário que possa comprometer o sistema.
O segundo ponto é a velocidade. Quando falamos em desempenho, devemos considerar a velocidade, que é o quão rápido nosso código roda. O usuário não aceita esperar segundos por uma resposta em muitos casos, então é importante ter um tempo de resposta rápido, independentemente do tamanho da estrutura. Por exemplo, ao fazer uma busca em uma estrutura de dados com 10 mil objetos, no primeiro caso utilizamos lista, e no segundo, sets (conjuntos). Com listas, fazemos uma busca linear, objeto por objeto, enquanto com sets, fazemos uma busca constante. Durante o curso, entenderemos como transformar e identificar um algoritmo linear de um constante e traremos alternativas sobre como e quando utilizar esses conceitos. No exemplo, dependendo do número de nomes, a busca linear pode demorar muito, enquanto a busca com hashes é muito mais eficiente, retornando rapidamente.
Também falaremos sobre escalabilidade. Escalabilidade é pensar no sistema como se ele fosse evoluir para um número muito maior de requisições ou objetos, comportando-se bem tanto para números pequenos quanto grandes. Temos um exemplo de duas alternativas: a primeira faz append dos nomes de usuário no resultado, transformando-os em maiúsculos, percorrendo todo o dicionário de usuários. A segunda utiliza multiprocessamento, aproveitando todos os núcleos da máquina para paralelizar o processamento, tornando o sistema mais escalável e rápido, independentemente do tamanho da entrada. É importante escolher estruturas de dados corretas e mecanismos para otimizar o código, permitindo que a aplicação escale para muitos usuários sem gargalos.
Quando falamos em otimização, há uma frase clássica de Donald Knuth: "Otimização prematura é a raiz de todo mal." Isso significa que otimizar um código sem métricas para medir onde está o gargalo pode ser desnecessário. É importante ter métricas, medir tempos, utilizar profiling e ferramentas disponíveis para analisar onde está o gargalo e decidir se é realmente um problema. Otimizar apenas por otimizar, sem necessidade, pode não ser interessante, considerando o tempo do desenvolvedor, que pode ser mais caro do que adicionar memória ou processamento.
Em resumo, os quatro conceitos de desempenho são: eficiência, que é fazer mais com menos recursos; velocidade, que é ter um tempo de resposta rápido; escalabilidade, que é permitir que a aplicação cresça sem gargalos; e otimização, que deve ser feita com base em métricas e necessidades reais.
Nosso objetivo é garantir que o código continue performando bem, independentemente da carga. Ou seja, quando aumentamos a carga ou o número de requisições, devemos manter um desempenho excelente, com o código respondendo e processando rapidamente. A otimização deve ser uma melhoria contínua, sempre baseada em dados, utilizando métricas e profiling. Vamos aplicar os fundamentos discutidos durante o curso para que nossa otimização realmente agregue valor ao código.
Abordaremos também os principais pilares de desempenho: CPU, latência, throughput e memória. Veremos como eles se correlacionam e como podem ser utilizados no dia a dia. O primeiro pilar é a CPU e o wall time. São conceitos simples, mas que podem causar confusão. O tempo de CPU é o tempo de cálculo que a CPU precisa para executar uma tarefa. A diferença entre CPU e wall time é que o wall time mede o tempo total de execução, não apenas o tempo de CPU. Por exemplo, se um bloco de código faz requisições externas e espera por uma resposta para processar algo, esse tempo de espera é contado no wall time, mas não no CPU time. Isso é crucial ao medir métricas com profiling para identificar se o gargalo está no código ou no tempo de espera. Ambas as formas podem ser usadas para otimizar o código, mas é importante saber o que estamos calculando e qual é o real gargalo.
Temos um exemplo de código onde calculamos tanto o CPU time quanto o wall time. No meio do código, inserimos um time.sleep para simular uma espera, que poderia ser uma requisição externa ou ao banco de dados. Durante esse tempo, a CPU não está sendo utilizada. Utilizamos as duas formas de cálculo: o wall time com time e o CPU time com process_time, ambos da biblioteca time. Ao rodar o código, observamos que o wall time é aproximadamente 2 segundos maior que o CPU time, o que era esperado, pois sabemos que 2 segundos são gastos em espera. A CPU está sendo utilizada minimamente, apenas para cálculos específicos, que aparecem tanto no CPU time quanto no wall time, mas o time.sleep aparece apenas no wall time.
As próximas métricas são latência e throughput. Embora similares, medem aspectos diferentes. A latência é o tempo de espera por um trecho de código ou resposta de uma API. Já o throughput é um cálculo de vazão, definindo, por exemplo, quantas requisições por segundo o código realiza. Ambas as métricas são correlacionadas. Com o throughput, podemos calcular quantas requisições uma API suporta por minuto, o que é importante para medir quantos resultados podemos gerar para os usuários em um determinado período. Temos um exemplo de código simulando uma requisição, onde calculamos o tempo de latência, que é 1 segundo, e o throughput, que resulta em 9 requisições por segundo, considerando 10 execuções divididas pelo tempo total.
O último pilar é a memória. Ao longo do curso, veremos que diferentes estruturas de dados ocupam diferentes espaços na memória. Algumas ocupam espaço mínimo, enquanto outras ocupam espaço desnecessário. É crucial monitorar o consumo de memória, pois isso pode afetar o desempenho da aplicação e de outras que rodam no mesmo cluster. Devemos estar atentos à utilização de memória durante a vida útil do código. Demonstramos três exemplos de estruturas de dados em Python: listas, tuplas e geradores. Utilizando a biblioteca sys, medimos o tamanho dos objetos em memória. Listas ocupam cerca de 8 MB, tuplas quase o mesmo, mas geradores ocupam um espaço ínfimo e muitas vezes servem ao mesmo propósito. Veremos quando usar listas ou geradores, mas muitas vezes são intercambiáveis, proporcionando ganhos significativos ao trocar a estrutura de dados utilizada.
Tivemos uma visão clara sobre os pilares de desempenho. Discutimos a CPU, que mede o tempo de processamento necessário para executar instruções de código; a latência, que avalia a rapidez com que obtemos respostas; o throughput, que analisa a execução por fatia de tempo, como requisições por segundo; e a memória, que mede o espaço necessário no hardware para armazenar dados ou objetos. Esperamos que tenham compreendido os fundamentos e métricas principais de desempenho. Nos vemos no próximo vídeo.
Olá, pessoal. Sejam bem-vindos a mais um vídeo do curso de desempenho em otimização de aplicações. No vídeo de hoje, trouxemos alguns extras para considerarmos ao realizar benchmarks de nossas aplicações. Vamos explorar isso com mais detalhes posteriormente, mas por enquanto, apresentamos uma prévia para entendermos melhor quais são as principais métricas afetadas por alguns fatores em nossa aplicação durante um benchmark.
Por exemplo, quando há muitas consultas de disco, de rede ou em bancos de dados, isso afeta significativamente a latência do nosso código. Ou seja, o tempo de espera aumenta, e nosso código pode apresentar um gargalo, demorando mais para responder ao usuário ou ao sistema. Uma das dicas que podemos oferecer previamente é a utilização de buffers, caching e async quando possível. Mais adiante, veremos como aplicar essas técnicas em nosso código.
Outro fator interessante é o garbage collector. Ele também pode afetar benchmarks de aplicação, dependendo de como os objetos estão sendo criados ou destruídos. Isso obviamente afeta a CPU e a memória. Quando precisamos realizar um benchmark, podemos desativar o garbage collector para identificar os gargalos da aplicação. No entanto, muitas vezes, o garbage collector também pode ser um gargalo. Por exemplo, se o garbage collector leva 10 milissegundos para criar e destruir 10 mil objetos, isso pode ser um gargalo em uma aplicação que precisa responder em 30 milissegundos. Portanto, é importante ficarmos atentos ao fato de que o garbage collector também é um fator relevante a ser analisado nos benchmarks.
Outro aspecto a considerar é a versão do ambiente. Ao realizar um benchmark, devemos garantir que as versões e o ambiente utilizados sejam sempre os mesmos, para evitar resultados discrepantes. Por exemplo, se estamos rodando a aplicação com uma versão mais antiga do Python, ela pode ter um desempenho mais lento. Se o ambiente já está executando muitas outras aplicações, isso também pode impactar o benchmark. O ideal é sempre termos um container ou um ambiente propício para isso, a fim de realizar o benchmark e entender o comportamento da aplicação.
O último fator que trouxemos é a reprodutibilidade. A reprodutibilidade consiste em rodar nosso benchmark ou aplicação diversas vezes e obter resultados similares.
Quando não temos reprodutibilidade, a confiança nas medições é baixa. Por exemplo, se não conseguimos reproduzir um código de forma que as execuções tenham um consumo similar de tempo e memória, isso indica baixa confiabilidade. Uma das formas de resolver essa questão é utilizar a média das execuções. Executar o código apenas uma vez pode resultar em um resultado muito diferente de rodá-lo dez vezes e utilizar a média, pois a média elimina os outliers da execução, proporcionando uma visão melhor sobre os recursos consumidos, como tempo e espaço.
Neste slide, apresentamos o que será abordado durante o curso. Vamos entender como medir e obter métricas como tempo de CPU, latência, throughput e memória, analisadas no vídeo anterior. Vamos executar nosso código e analisar os dados utilizando profilers e outras ferramentas.
Também veremos como otimizar o código. Após identificar onde está o gargalo ou problema, aprenderemos técnicas para melhorar a performance do trecho de código, evitando que ele se torne um gargalo à medida que a aplicação cresce. Vamos explorar como fazer com que o tempo de execução seja constante ao invés de linear, ou linear ao invés de quadrático. Ao longo do curso, aprenderemos a tornar nosso código mais rápido e eficiente, consumindo menos recursos do processador.
Discutiremos também quando otimizar. Muitas vezes, não vale a pena substituir uma função específica por uma função built-in ou executada em C, pois isso pode não trazer um ganho real de otimização. Assim, não vale a pena investir tempo em algo que não trará benefícios significativos. Teremos uma noção geral de quando realmente precisamos otimizar, baseando-nos nos resultados apresentados no benchmark. Quando necessário, faremos um refactor para melhorar o comportamento do código.
Exploraremos diversas técnicas para que nosso código escale melhor e responda melhor aos inputs do usuário e do sistema. Veremos just-in-time compilation, comparativos de complexidade entre as principais estruturas de dados, e quando é melhor utilizar um set ao invés de uma lista, ou um dicionário, ou uma estrutura de dados mais eficiente e que consuma menos memória.
Abordaremos conceitos como caching, para armazenar dados já processados e evitar reprocessamento. Analisaremos alguns aspectos internos do Python, como JIT, geração de bytecode pelo interpretador, e como implementar paralelismo em Python utilizando threads e multiprocessamento. Teremos também uma análise de alto nível de sistemas, incluindo o uso de load balancers para tornar a aplicação mais escalável, entre outros conceitos de design systems e sistemas que precisam escalar para milhares ou milhões de usuários.
Esperamos que tenham gostado da primeira aula. Até a próxima!
O curso Python: Otimização de desempenho e integração com C possui 249 minutos de vídeos, em um total de 68 atividades. Gostou? Conheça nossos outros cursos de Python em Programação, ou leia nossos artigos de Programação.
Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:
Impulsione a sua carreira com os melhores cursos e faça parte da maior comunidade tech.
2 anos de Alura
Matricule-se no plano PLUS 24 e garanta:
Jornada de estudos progressiva que te guia desde os fundamentos até a atuação prática. Você acompanha sua evolução, entende os próximos passos e se aprofunda nos conteúdos com quem é referência no mercado.
Mobile, Programação, Front-end, DevOps, UX & Design, Marketing Digital, Data Science, Inovação & Gestão, Inteligência Artificial
Formações com mais de 1500 cursos atualizados e novos lançamentos semanais, em Programação, Inteligência Artificial, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.
A cada curso ou formação concluído, um novo certificado para turbinar seu currículo e LinkedIn.
No Discord, você participa de eventos exclusivos, pode tirar dúvidas em estudos colaborativos e ainda conta com mentorias em grupo com especialistas de diversas áreas.
Faça parte da maior comunidade Dev do país e crie conexões com mais de 120 mil pessoas no Discord.
Acesso ilimitado ao catálogo de Imersões da Alura para praticar conhecimentos em diferentes áreas.
Explore um universo de possibilidades na palma da sua mão. Baixe as aulas para assistir offline, onde e quando quiser.
Acelere o seu aprendizado com a IA da Alura e prepare-se para o mercado internacional.
2 anos de Alura
Todos os benefícios do PLUS 24 e mais vantagens exclusivas:
Luri é nossa inteligência artificial que tira dúvidas, dá exemplos práticos, corrige exercícios e ajuda a mergulhar ainda mais durante as aulas. Você pode conversar com a Luri até 100 mensagens por semana.
Aprenda um novo idioma e expanda seus horizontes profissionais. Cursos de Inglês, Espanhol e Inglês para Devs, 100% focado em tecnologia.
Para estudantes ultra comprometidos atingirem seu objetivo mais rápido.
2 anos de Alura
Todos os benefícios do PRO 24 e mais vantagens exclusivas:
Mensagens ilimitadas para estudar com a Luri, a IA da Alura, disponível 24hs para tirar suas dúvidas, dar exemplos práticos, corrigir exercícios e impulsionar seus estudos.
Envie imagens para a Luri e ela te ajuda a solucionar problemas, identificar erros, esclarecer gráficos, analisar design e muito mais.
Escolha os ebooks da Casa do Código, a editora da Alura, que apoiarão a sua jornada de aprendizado para sempre.
Conecte-se ao mercado com mentoria individual personalizada, vagas exclusivas e networking estratégico que impulsionam sua carreira tech para o próximo nível.