C# - Inicializando listas para aumentar o desempenho


 Nesta dica veremos como aumentar o desempenho inicializando listas do tipo List<T>.

Uma List<T> é uma classe genérica na linguagem C# que representa uma lista dinâmica de elementos do tipo T. Essa classe é parte do namespace System.Collections.Generic e fornece uma estrutura de dados flexível para armazenar e manipular itens.

Algumas coleções, como List<T>, têm um tamanho inicial predefinido e sempre que você adiciona um novo item à coleção, há dois cenários:

1- A coleção tem espaço livre, alocado, mas ainda não preenchido, portanto, adicionar um item é imediato;

2- A coleção já está cheia: internamente, o .NET redimensiona a coleção, para que na próxima vez que você adicionar um novo item, voltemos à opção nº 1.


Claramente, a segunda abordagem tem um impacto no desempenho geral. Podemos provar isso ?

Sim, podemos testar o desempenho da alocação de memória e realocação em tempo de execução da coleção List<T> no C#.

Podemos fazer isso criando um loop que adiciona um grande número de elementos a uma lista vazia. Podemos então comparar o tempo de execução de duas listas: uma com um tamanho inicial definido e outra sem um tamanho inicial definido.

Por exemplo, o código a seguir cria duas listas: uma com um tamanho inicial definido de 100000 elementos e outra sem um tamanho inicial definido. O código então adiciona 1000000 elementos a cada lista e mede o tempo necessário para concluir a operação:

Nota: Este código foi criado no VS 2022 usando o recurso Top Level Statement

using System.Diagnostics;

const int NUM_ITENS = 1000000;

const int INITIAL_CAPACITY = 100000;

var stopwatch = new Stopwatch();

stopwatch.Start();

// Lista com tamanho inicial definido

var lista1 = new List<int>(INITIAL_CAPACITY);

for (int i = 0; i < NUM_ITENS; i++)
{
   lista1.Add(i);
}

stopwatch.Stop();

Console.WriteLine($"Tempo para adicionar {NUM_ITENS} itens a uma lista
  com tamanho inicial de
{INITIAL_CAPACITY}: {stopwatch.ElapsedMilliseconds} ms");

stopwatch.Reset();
stopwatch.Start();

// Lista sem tamanho inicial definido

var lista2 = new List<int>();

for (int i = 0; i < NUM_ITENS; i++)
{
   lista2.Add(i);
}

stopwatch.Stop();

Console.WriteLine($"Tempo para adicionar {NUM_ITENS} itens a uma
                    lista sem tamanho inicial definido:
{stopwatch.ElapsedMilliseconds} ms");

Console.ReadKey();

Executando o projeto iremos obter o seguinte resultado:

.

Os resultados mostram que a lista com um tamanho inicial definido tem um desempenho melhor do que a lista sem um tamanho inicial definido. Isso ocorre porque a lista com tamanho inicial definido não precisa realocar a memória para acomodar novos itens, enquanto a lista sem tamanho inicial definido precisa realocar a memória quando a capacidade atual é excedida.

Dessa forma, para obter o melhor desempenho ao usar a coleção List<T>, recomenda-se seguir as seguintes práticas:

  1. Sempre que possível, é recomendável definir um tamanho inicial para a lista ao criá-la, usando o construtor que recebe um parâmetro de capacidade inicial. Isso evita a realocação de memória desnecessária e melhora o desempenho da operação de adição de elementos.
     
  2. É recomendável evitar realocações desnecessárias de memória na lista, adicionando elementos em blocos grandes em vez de um de cada vez. Isso reduz a frequência de realocação de memória e melhora o desempenho da operação de adição de elementos.
     
  3. É importante definir a capacidade correta da lista para evitar desperdício de memória. Se a capacidade definida for muito pequena, a lista precisará ser realocada com mais frequência, o que afetará o desempenho. Se a capacidade definida for muito grande, haverá desperdício de memória.
     
  4. É importante usar uma lista com um tipo de dados apropriado para evitar conversões desnecessárias entre tipos de dados e melhorar o desempenho da operação de adição de elementos.
     
  5. É recomendável evitar inserções e exclusões de elementos no meio da lista, pois isso pode causar realocações desnecessárias de memória. Se inserções e exclusões no meio da lista forem necessárias, é recomendável usar uma estrutura de dados diferente, como uma lista vinculada.

No entanto, é importante notar que o desempenho pode variar dependendo do tamanho inicial da lista e do número de elementos adicionados. Em geral, é recomendável definir um tamanho inicial para a lista sempre que possível para melhorar o desempenho em cenários de grande volume de dados.

E estamos conversados ...

"Portanto, nada julgueis antes de tempo, até que o Senhor venha, o qual também trará à luz as coisas ocultas das trevas, e manifestará os desígnios dos corações; e então cada um receberá de Deus o louvor."
1 Coríntios 4:5

Referências:


José Carlos Macoratti