High Swap Space Usage — List

Alex Tochetto
4 min readMar 23, 2021

--

Recentemente nos deparamos com a situação de high swap space usage esta situação foi evidenciada pelo alerta configurado pelo DevOps.

Isto significa que, em algum momento a aplicação está descarregando algum objeto que está na stack (não na HEAP) para o disco, liberando memória. Porém este objeto pode ser utilizado em outras partes da aplicação e quando requisitado ele volta para a memória, esta troca é chamada de swap. Este processo com certeza deixa a aplicação mais lenta.

List < T >

As listas são grandes indicativos de que estão causando situações como esta. Existem formas de utilizar as listas genéricas que minimizam o swap. Vou falar um pouco sobre como utilizar uma lista de forma que não estamos habituados.

O primeiro ponto que eu gostaria de destacar está na documentação, a lista genérica possui um parâmetro em seu construtor que é o capacity.

Para analisar o código, sugiro usar o ILSpy ou uma ferramenta de sua preferência para entender o que o método Add() em uma lista faz. Há também o Reference Sources da Microsoft que pode ajudar e que é web.

Quando o capacity é especificado no construtor, o _items ( T[] - array de T) será criado com o número de elementos passado no capacity. A imagem a seguir exemplifica o capacity como parâmetro do construtor.

Na imagem a seguir, há uma comparação do _size (.Count da lista) com o _items.Length.

Quando o Add() é chamado para adicionar algo em uma lista, o EnsureCapacity(int min) somente é chamado quando o número de itens adicionados na lista for igual ao número de itens inicializado previamente por conta do capacity conforme imagem a seguir

O método EnsureCapacity(int min) só é chamado quando o _items não tenha sido inicializado (não é o caso quando o capacity é especificado) ou quando chegou ao seu limite. A imagem a seguir mostra os detalhes do método.

  • Se o capacity não é passado no construtor o _items já é inicializado com 4 itens.
  • Quando o número de itens da lista chega a 4 elementos o número de _items passa para 8 e assim por diante.

Neste momento temos um crescimento da lista de forma desnecessária e é neste ponto que os problemas de swap começam.

A grande questão aqui é quando de fato saberemos o tamanho da nossa lista para poder inicializá-la com o capacity esperado.

BenchmarkDotnet — List < T >

Eu utilizei o BenchmarkDotnet como ferramenta de testes de performance para melhorar a visualização da diferença de velocidade, consumo de memória, pressão sobre o GC (Garbage Collector), etc.

Na imagem a seguir eu realizei 2 testes:

  • FixedGenericListCapacity(): capacity com valor 10.
  • NoGenericListCapacity(): sem capacity.

É possível analisar que a alocação de memória (última coluna) da imagem a seguir, é substancialmente menor. Isso ajuda o GC no seu trabalho e evita que este objeto vá parar na Gen 1 ou 2.

Github

Este projeto está disponível no Github.

Para executá-lo basta compilar em release com a linha de comando a seguir

dotnet build -c Release .\BenchmarkDotNet-GenericList.csproj

Depois executar com a linha a seguir

dotnet .\bin\Release\netcoreapp3.1\BenchmarkDotNet-GenericList.dll

BenchmarkDotnet — StringBuilder

Eu aproveitei e fiz o mesmo teste com o StringBuilder, que também possui o capacity sem seu construtor, veja o resultado a seguir.

Conclusão

Sempre que você souber o tamanho da sua lista, use o capacity, nem sempre isso é possível.

Originally published at http://alextochetto.com on March 23, 2021.

--

--