Tornando sua aplicação Go mais eficiente: Estratégias de otimização de desempenho
Tags: go, programming
Published: 2023-06-28 21:02:06

**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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j9xdl1naq2frmwp8avo4.png)

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.