Existe um impacto no desempenho ao chamar ToList ()?

Ao usar ToList() , há um impacto no desempenho que precisa ser considerado?

Eu estava escrevendo uma consulta para recuperar arquivos de um diretório, que é a consulta:

string[] imageArray = Directory.GetFiles(directory);

No entanto, desde que eu gosto de trabalhar com o List , decidi colocar …

List imageList = Directory.GetFiles(directory).ToList();

Então, existe algum tipo de impacto no desempenho que deve ser considerado ao decidir fazer uma conversão como essa – ou apenas para ser considerado quando se lida com um grande número de arquivos? Esta é uma conversão insignificante?

IEnumerable.ToList()

Sim, IEnumerable.ToList() tem um impacto no desempenho, é uma operação O (n) , embora provavelmente só requeira atenção em operações críticas de desempenho.

A operação ToList() usará o construtor List(IEnumerable collection) . Esse construtor deve fazer uma cópia da matriz (mais geralmente IEnumerable ), caso contrário futuras modificações da matriz original serão alteradas na origem T[] também que não seria desejável geralmente.

Gostaria de reiterar isso só vai fazer a diferença com uma lista enorme, copiando pedaços de memory é uma operação bastante rápida para executar.

Dica útil, As vs To

Você notará no LINQ que há vários methods que começam com As (como AsEnumerable() ) e To (como ToList() ). Os methods que começam com To exigir uma conversão como acima (isto é, podem afetar o desempenho), e os methods que começam com As não requerem apenas uma operação conjurada ou simples.

Detalhes adicionais na List

Aqui está um pouco mais detalhado sobre como o List funciona caso você esteja interessado 🙂

Uma List também usa uma construção chamada matriz dinâmica que precisa ser redimensionada sob demanda, esse evento de redimensionamento copia o conteúdo de uma matriz antiga para a nova matriz. Por isso, começa pequeno e aumenta de tamanho, se necessário .

Essa é a diferença entre os atributos Capacity e Count em List . Capacity refere-se ao tamanho do array nos bastidores, Count é o número de itens na List que é sempre < = Capacity . Assim, quando um item é adicionado à lista, aumentando-o além da Capacity , o tamanho da List é dobrado e a matriz é copiada.

Existe um impacto no desempenho ao chamar toList ()?

Sim, claro. Teoricamente, mesmo o i++ tem um impacto no desempenho, ele atrasa o programa para talvez alguns ticks.

O que o .ToList faz?

Quando você invoca .ToList , o código chama Enumerable.ToList() que é um método de extensão que return new List(source) . No construtor correspondente, na pior das hipóteses, ele passa pelo contêiner de itens e os adiciona um a um em um novo contêiner. Portanto, seu comportamento afeta pouco o desempenho. É impossível ser um gargalo de desempenho da sua aplicação.

O que há de errado com o código na pergunta

Directory.GetFiles passa pela pasta e retorna todos os nomes dos arquivos imediatamente para a memory, há um risco potencial de que a string [] gere bastante memory, reduzindo a velocidade de tudo.

O que deve ser feito então

Depende. Se você (assim como sua lógica de negócios) garante que o valor do arquivo na pasta seja sempre pequeno, o código é aceitável. Mas ainda é sugerido usar uma versão preguiçosa: Directory.EnumerateFiles no C # 4. Isso é muito mais parecido com uma consulta, que não será executada imediatamente, você pode adicionar mais consultas sobre isso, como:

 Directory.EnumerateFiles(myPath).Any(s => s.Contains("myfile")) 

que irá parar de procurar o caminho assim que um arquivo cujo nome contenha “myfile” for encontrado. Isso obviamente tem um desempenho melhor que o .GetFiles .

Existe um impacto no desempenho ao chamar toList ()?

Sim existe. Usando o método de extensão Enumerable.ToList() irá construir um novo object List da coleção de fonts IEnumerable que obviamente tem um impacto no desempenho.

No entanto, entender List pode ajudá-lo a determinar se o impacto no desempenho é significativo.

List usa uma matriz ( T[] ) para armazenar os elementos da lista. Arrays não podem ser estendidos uma vez que eles são alocados, portanto, List usará uma matriz de tamanho excessivo para armazenar os elementos da lista. Quando a List cresce além do tamanho da matriz subjacente, uma nova matriz deve ser alocada e o conteúdo da matriz antiga deve ser copiado para a nova matriz maior antes que a lista possa crescer.

Quando uma nova List é construída a partir de um IEnumerable há dois casos:

  1. A coleção de origem implementa ICollection : Em seguida, ICollection.Count é usado para obter o tamanho exato da coleção de origem e uma matriz de apoio correspondente é alocada antes de todos os elementos da coleção de origem serem copiados para a matriz de apoio usando ICollection.CopyTo() . Essa operação é bastante eficiente e, provavelmente, será mapeada para algumas instruções da CPU para copiar blocos de memory. No entanto, em termos de desempenho, a memory é necessária para o novo array e os ciclos de CPU são necessários para copiar todos os elementos.

  2. Caso contrário, o tamanho da coleção de origem é desconhecido eo enumerador de IEnumerable é usado para adicionar cada elemento de origem um por vez para a nova List . Inicialmente, a matriz de apoio está vazia e uma matriz de tamanho 4 é criada. Então, quando este array é muito pequeno, o tamanho é duplicado, então o array de backing cresce assim, 4, 8, 16, 32, etc. Toda vez que o backing array cresce, ele tem que ser realocado e todos os elementos armazenados devem ser copiados. Essa operação é muito mais cara em comparação com o primeiro caso em que uma matriz do tamanho correto pode ser criada imediatamente.

    Além disso, se sua coleção de fonts contiver 33 elementos, a lista terminará usando uma matriz de 64 elementos, desperdiçando alguma memory.

No seu caso, a coleção de fonts é uma matriz que implementa ICollection para que o impacto no desempenho não seja algo com o qual você deve se preocupar a menos que sua matriz de origem seja muito grande. Chamar ToList() irá simplesmente copiar a matriz de origem e envolvê-la em um object List . Mesmo o desempenho do segundo caso não é algo para se preocupar com pequenas collections.

“existe um impacto no desempenho que precisa ser considerado?”

O problema com o cenário exato é que, em primeiro lugar, sua preocupação real com o desempenho seria a velocidade e a eficiência do disco rígido da unidade.

Dessa perspectiva, o impacto é certamente insignificante a ponto de NÃO ser necessário considerá-lo.

MAS SÓ se você realmente precisar dos resources da estrutura List<> para torná-lo mais produtivo, ou seu algoritmo mais amigável, ou alguma outra vantagem. Caso contrário, você está propositadamente adicionando um impacto insignificante no desempenho, sem nenhuma razão. Nesse caso, naturalmente, você não deveria fazer isso! 🙂

ToList() cria uma nova lista e coloca os elementos nela, o que significa que há um custo associado ao fazer ToList() . No caso de uma pequena coleção, não será um custo muito perceptível, mas ter uma coleção enorme pode causar um impacto no desempenho no caso de usar o ToList.

Geralmente você não deve usar ToList () a menos que o trabalho que você está fazendo não possa ser feito sem converter a coleção para List. Por exemplo, se você quiser apenas percorrer a coleção, não será necessário executar o ToList

Se você estiver realizando consultas em uma fonte de dados, por exemplo, um database usando LINQ para SQL, o custo de fazer ToList é muito mais porque quando você usa ToList com LINQ to SQL em vez de execução atrasada ou seja, itens de carga quando necessário (o que pode ser benéfico em muitos cenários) instantaneamente carrega itens do database na memory

Será tão (in) eficiente quanto fazer:

 var list = new List(items); 

Se você desmontar o código-fonte do construtor que leva um IEnumerable , verá que ele fará algumas coisas:

  • Chame collection.Count , portanto, se collection for um IEnumerable , isso forçará a execução. Se collection é uma matriz, lista, etc., deve ser O(1) .

  • Se a collection implementar ICollection , ela salvará os itens em uma matriz interna usando o método ICollection.CopyTo . Deve ser O(n) , sendo n o comprimento da coleção.

  • Se a collection não implementar ICollection , iterará pelos itens da coleção e os adicionará a uma lista interna.

Assim, sim, consumirá mais memory, já que tem que criar uma nova lista, e no pior dos casos, será O(n) , já que iterará através da collection para fazer uma cópia de cada elemento.

ToList Irá criar uma nova lista e copiar elementos da fonte original para a lista recém-criada, de modo que a única coisa é copiar os elementos da fonte original e depende do tamanho da fonte

Considerando o desempenho da recuperação da lista de arquivos, ToList() é insignificante. Mas não para outros cenários. Isso realmente depende de onde você está usando.

  • Ao chamar uma matriz, lista ou outra coleção, você cria uma cópia da coleção como uma List . O desempenho aqui depende do tamanho da lista. Você deve fazer isso quando for realmente necessário.

    No seu exemplo, você chama isso em uma matriz. Ele itera sobre a matriz e adiciona os itens, um por um, a uma lista recém-criada. Portanto, o impacto no desempenho depende do número de arquivos.

  • Ao chamar um IEnumerable , você materializa o IEnumerable (geralmente uma consulta).