DEV Community

Matheus Figueiredo
Matheus Figueiredo

Posted on

Tornando sua aplicação Go mais eficiente: Estratégias de otimização de desempenho

De volta aos estudos em Golang, decidi criar este artigo para documentar a minha jornada com esta linguagem. Particularmente eu aprendo muito ao criar algum tipo de conteúdo.

Sabemos que desenvolver aplicações eficientes e com bom desempenho é essencial para fornecer uma boa experiência aos usuários, mas o que de fato significa uma aplicação eficiente?

Uma aplicação eficiente é aquela que utiliza os recursos de forma otimizada, entregando um bom desempenho e consumindo uma quantidade adequada de recursos, como memória e processamento. Uma aplicação eficiente é capaz de lidar com cargas de trabalho elevadas, responder rapidamente às solicitações dos usuários e minimizar o consumo excessivo de recursos, resultando em uma experiência mais ágil e eficiente.

É claro que a escolha por uma linguagem de programação passa por muitos contextos e requisitos, entretanto o objetivo aqui é tornar uma aplicação eficiente, no cerne da palavra, e nisso o Go é extremamente capaz.

Separei 3 tópicos que são diferenciais da linguagem e que podem elevar o nível da sua aplicação:

1 - Utilizar eficientemente as estruturas de dados
2 - Aproveitar a Concorrência
3 - Otimizar o uso de memória


1 - Utilizar eficientemente as estruturas de dados

Temos algumas estruturas de dados em Go, para diversos fins.

Escolher a estrutura de dados correta pode impactar significativamente o desempenho, a escalabilidade e a legibilidade do código.

Slices:
Slices são uma das estruturas mais utilizadas em Go. O grande ganho aqui é a flexibilidade para manipular coleções de elementos, de tamanho variável, enquanto no Array precisamos definir o seu tamanho previamente.

difference between slice and array

Ao trabalhar com uma slice, você especifica apenas o tamanho inicial da slice. À medida que você adiciona elementos à slice, ela pode crescer automaticamente, conforme necessário, alocando apenas a quantidade necessária de memória adicional.

Em vez de alocar uma quantidade fixa de memória no início e correr o risco de ficar sem espaço, as slices aumentam sua capacidade gradualmente, conforme necessário. Isso é especialmente útil quando você não sabe antecipadamente o número exato de elementos que serão armazenados na slice.

Maps:
Maps são estrutura de dados chave-valor, permitindo que você associe uma chave a um valor correspondente.

Temos diversos problemas que o map resolve, mas alguns deles que me chamaram atenção foram:

1 - Indexação rápida: É muito mais otimizado gerenciar usuários (Através de ID, por ex.), utilizando map. Podemos usar um map com o ID do usuário como chave e o objeto de usuário correspondente como valor, permitindo uma busca rápida de usuários com base em seus IDs.

2 - Cache de resultados: Em aplicações que envolvem cálculos pesados ou acesso a recursos externos, um map pode ser usado como cache para armazenar resultados pré-computados ou resultados obtidos anteriormente. Isso evita a necessidade de recalcular ou reacessar o recurso a todo o tempo que este é executado.

Structs:
Structs são estruturas de dados que definem a forma como os dados de um arquivo por ex. são organizados e armazenados.

O GO não é uma linguagem orientada a objetos (OOP), porém a struct é uma forma que permite utilizar um dos conceitos que mais gosto na OOP, que é a Inversão de Dependência e Injeção de Dependência.

Ao projetar uma struct, eu posso adotar esses conceitos acima para criar componentes mais independentes e desacoplados. As dependências externas são injetadas nas structs em vez de serem criadas dentro delas. Isso permite que as structs dependam de interfaces ou abstrações em vez de classes concretas, invertendo as dependências.


2 - Aproveitar a Concorrência

Concorrência é uma técnica que permite que diferentes partes de um programa sejam executadas simultaneamente, melhorando a performance geral.

De forma geral, a concorrência permite que diferentes partes de um programa sejam executadas independentemente umas das outras, reduzindo o tempo de execução total. Isso é especialmente útil em situações em que há tarefas que podem ser realizadas em paralelo, como processamento de requisições em um servidor web

No entanto, é importante mencionar que a concorrência não é uma solução para todos os problemas de desempenho. Em alguns casos, a utilização excessiva de goroutines pode resultar em sobrecarga e piorar o desempenho. É necessário equilibrar.


3 - Otimizar o uso de memória

Temos muitos recursos na linguagem para otimizar o uso da memória, destaco algumas:

Otimizações do compilador: O compilador Go realiza várias otimizações, como inlining de funções e eliminação de código morto, para melhorar a performance e reduzir o uso de memória.

Garbage collector: O Go gerencia automaticamente a alocação e a liberação de memória, evitando que esse gerenciamento seja de forma manual. Assim, é reduzido a ocorrência de vazamentos e o processo se torna mais eficiente.

Uso das goroutines: Por fim, as goroutines permitem a execução concorrente das tarefas (lembra do tópico 2?). Elas possuem uma representação de memória menor do que as threads tradicionais, o que significa que é possível executar um grande número de goroutines com uso eficiente de memória.


Muitas aplicações e empresas exigem esse nível de otimização, pois isso se traduz, no fim do dia, em custos.

Portanto, convido e recomendo vocês a explorararem o mundo do Go e descobrir como essa linguagem pode ser uma aliada poderosa na criação de aplicações de alto desempenho, otimizando o uso de recursos de memória e oferecendo uma experiência excepcional aos usuários.

Top comments (0)