Qual a diferença de length e capacity

Quando trabalhamos com slices, existem duas funções essenciais que utilizamos para “medir” o tamanho (length) e a capacidade (capacity) de um slice, que são len e cap, respectivamente.

Mas afinal, qual a diferença de length e capacity?

A documentação do Go define length como sendo a quantidade de elementos em um slice, enquanto capacity é a capacidade do array para abrigar elementos.

No exemplo abaixo vamos criar 2 slices. No primeiro vamos definir somente o length, enquanto no segundo vamos definir o length e a capacity.

sl1 := make([]string, 10)
sl2 := make([]string, 10, 20)

fmt.Println("sl1", len(sl1), cap(sl1))
fmt.Println("sl2", len(sl2), cap(sl2))

// output
sl1 10 10
sl2 10 20

Como podemos ver, quando não passamos o terceiro parâmetro da função make a capacidade do slice terá o mesmo valor que o tamanho.

Para demonstrar a diferença a nível de performance, vamos escrever duas funções. Na primeira vamos iniciar o slice com X de length e depois expandi-lo até seu tamanho ser 2X. Na segunda, vamos iniciar o slice com X de length e 2X de capacity.

package main

import "strings"

var letters = []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"}

func Len() []string {
    sl1 := make([]string, len(letters))
    for key, letter := range letters {
        sl1[key] = letter
    }

    for _, letter := range letters {
        sl1 = append(sl1, strings.ToUpper(letter))
    }

    return sl1
}

func Cap() []string {
    sl2 := make([]string, len(letters), len(letters)*2)
    for key, letter := range letters {
        sl2[key] = letter
    }

    for _, letter := range letters {
        sl2 = append(sl2, strings.ToUpper(letter))
    }

    return sl2
}

Agora vamos escrever um pequeno benchmark para analisar a diferença das duas funções.

package main

import (
    "testing"
)

var result []string

func BenchmarkLen(b *testing.B) {
    var r []string
    for n := 0; n < b.N; n++ {
        r = Len()
    }

    result = r
}

func BenchmarkCap(b *testing.B) {
    var r []string
    for n := 0; n < b.N; n++ {
         r = Cap()
    }

    result = r
}

Feito isso, vamos executar o benchmark com o seguinte comando:

$ go test -bench=. -cpu=8 -benchmem -benchtime=5s -count 5
benchmark len vs cap

Como podemos ver nos resultados acima, a função Cap tem 1 alocação a menos por operação, consumindo 33% menos bytes por operação com um tempo médio 20% mais baixo por operação, o que no final faz com que essa função consiga executar um número maior de operações.

Dessa forma, em casos onde determinado array sempre terá tamanho entre X e Y, a melhor estratégia é criar ele usando length X e capacity Y. Um exemplo pode ser iterar sobre os resultados de uma consulta no banco de dados com limite Y, pois sabemos que o retorno será entre 0 e Y.

Claro que, caso você saiba a quantidade exata retornada na consulta, o ideal é criar o array no tamanho exato pois a performance seria melhor ainda.

Deixem suas dúvidas nos comentários.

Até a próxima!


Subscreva

Fique por dentro de tudo o que acontece no mundo Go.

Deixe uma resposta