Comparação de slices em Go: otimização e desempenho

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Comparação de slices em Go: como não perder desempenho e não enlouquecer

\n\n

Imagine a situação: você está escrevendo um serviço de alta carga em Go e precisa comparar dois slices de bytes ou strings. Parece simples, não é? Mas o diabo, como sempre, está nos detalhes. No Stack Overflow, a pergunta "Checking the equality of two slices" acumulou milhares de visualizações — e não é à toa. Neste artigo, vamos analisar quais armadilhas esperam o desenvolvedor, como escrever uma comparação correta e, principalmente, como fazer isso da forma mais rápida possível.

\n\n

Não usaremos definições acadêmicas chatas. Em vez disso, exemplos práticos, benchmarks e dicas que você pode aplicar hoje mesmo. Vamos nessa!

\n\n

Por que a comparação simples nem sempre é a correta?

\n\n

Em Go, um slice não é um array. É um descritor que contém um ponteiro para o array subjacente, comprimento e capacidade. Portanto, o operador == não funciona para slices. Tentar escrever slice1 == slice2 resultará em um erro de compilação:

\n\n
invalid operation: slice1 == slice2 (slice can only be compared to nil)
\n\n

Isso não é um bug, é uma feature. Os desenvolvedores do Go proibiram intencionalmente a comparação direta de slices porque isso pode levar a consequências não óbvias. Por exemplo, dois slices podem referenciar o mesmo array subjacente, mas ter comprimentos diferentes. Ou o contrário — arrays diferentes, mas dados idênticos.

\n\n

Então, o que fazer? Existem várias abordagens, e vamos analisar cada uma delas.

\n\n

Método 1: Loop manual (básico, mas confiável)

\n\n

A maneira mais óbvia é escrever uma função que percorre todos os elementos e os compara um a um. Isso dá controle total sobre o processo.

\n\n
func EqualSlices(a, b []int) bool {\n    if len(a) != len(b) {\n        return false\n    }\n    for i := range a {\n        if a[i] != b[i] {\n            return false\n        }\n    }\n    return true\n}
\n\n

Prós: transparência, sem dependências, possibilidade de personalização (por exemplo, comparação com tolerância para float).
\nContras: precisa ser escrito para cada tipo de dado.

\n\n

Método 2: reflect.DeepEqual (universal, mas lento)

\n\n

Na biblioteca padrão, existe a função reflect.DeepEqual, que consegue comparar qualquer estrutura de dados — slices, maps, structs. Fica assim:

\n\n
import "reflect"\n\nfunc main() {\n    a := []int{1, 2, 3}\n    b := []int{1, 2, 3}\n    fmt.Println(reflect.DeepEqual(a, b)) // true\n}
\n\n

Parece ideal, mas há um detalhe: reflect.DeepEqual usa reflexão, e isso é sempre caro. Em slices pequenos, a diferença é imperceptível, mas se você tiver milhares de elementos — o desempenho cairá drasticamente.

\n\n

Importante: reflect.DeepEqual também considera nil e um slice vazio como diferentes! Isso pode ser uma surpresa:

\n\n
var a []int = nil\nb := []int{}\nfmt.Println(reflect.DeepEqual(a, b)) // false!
\n\n

Método 3: bytes.Equal para slices de bytes (ideal)

\n\n

Se você trabalha com []byte, a melhor escolha é a função bytes.Equal da biblioteca padrão. Ela é escrita em assembly e é altamente otimizada:

\n\n
import "bytes"\n\nfunc main() {\n    a := []byte("hello")\n    b := []byte("hello")\n    fmt.Println(bytes.Equal(a, b)) // true\n}
\n\n

Esta é a maneira mais rápida para slices de bytes. Internamente, ela usa memcmp no nível C, o que proporciona um enorme ganho de desempenho.

\n\n

Método 4: Comparação com crypto/subtle (para criptografia)

\n\n

Menção especial merece o pacote crypto/subtle. Sua função ConstantTimeCompare é projetada para comparar dados secretos (senhas, tokens) sem vazamento de tempo. A comparação comum pode ser suscetível a ataques de timing:

\n\n
import "crypto/subtle"\n\nfunc                

Blogs

Book Recommendations