Frequentemente, encontro o caso em que quero avaliar uma consulta exatamente onde a declaro. Isso geralmente ocorre porque eu preciso iterá-lo várias vezes e é caro calcular. Por exemplo:
string raw = "...";
var lines = (from l in raw.Split('\n')
let ll = l.Trim()
where !string.IsNullOrEmpty(ll)
select ll).ToList();
Isso funciona bem. Mas se não vou modificar o resultado, é melhor ligar em ToArray()
vez de ToList()
.
Gostaria de saber, no entanto, se ToArray()
é implementado pela primeira chamada ToList()
e, portanto, é menos eficiente de memória do que apenas a chamada ToList()
.
Eu sou louco? Devo apenas ligar ToArray()
- em segurança, sabendo que a memória não será alocada duas vezes?
.net
linq
performance
Frank Krueger
fonte
fonte
Respostas:
A menos que você simplesmente precise de uma matriz para atender a outras restrições que deve usar
ToList
. Na maioria dos cenáriosToArray
, alocará mais memória queToList
.Ambos usam matrizes para armazenamento, mas
ToList
têm uma restrição mais flexível. Ele precisa que a matriz seja pelo menos tão grande quanto o número de elementos na coleção. Se a matriz for maior, isso não é um problema. No entanto,ToArray
precisa que o array seja dimensionado exatamente para o número de elementos.Para atender a essa restrição,
ToArray
muitas vezes é necessário mais uma alocação do queToList
. Depois de ter uma matriz grande o suficiente, ela aloca uma matriz exatamente do tamanho correto e copia os elementos de volta para essa matriz. O único momento em que isso pode ser evitado é quando o algoritmo de crescimento da matriz coincide com o número de elementos que precisam ser armazenados (definitivamente em minoria).EDITAR
Algumas pessoas me perguntaram sobre a consequência de ter uma memória extra não utilizada no
List<T>
valor.Esta é uma preocupação válida. Se a coleção criada tiver vida longa, nunca for modificada após a criação e tiver uma grande chance de aterrissar no heap Gen2, é melhor aproveitar a alocação extra
ToArray
antecipadamente.Em geral, embora eu ache que este é o caso mais raro. É muito mais comum ver muitas
ToArray
chamadas que são imediatamente passadas para outros usos de memória de curta duração, caso em queToList
é comprovadamente melhor.A chave aqui é o perfil, perfil e, em seguida, perfil um pouco mais.
fonte
ToArray
Pode alocar mais memória se precisar do tamanho exato dos locais, ondeToList<>
obviamente tem os seus locais de reposição automáticos. (autoincrease)A diferença de desempenho será insignificante, pois
List<T>
é implementada como uma matriz de tamanho dinâmico. Chamar qualquer umToArray()
(que usa umaBuffer<T>
classe interna para aumentar a matriz) ouToList()
(que chama oList<T>(IEnumerable<T>)
construtor) acabará sendo uma questão de colocá-los em uma matriz e aumentar a matriz até que ela se ajuste a todos.Se você deseja confirmação concreta desse fato, confira a implementação dos métodos em questão no Reflector - você verá que eles se resumem a um código quase idêntico.
fonte
ToArray()
eToList()
é que o primeiro precisa aparar o excesso, o que envolve copiar toda a matriz, enquanto o segundo não aparar o excesso, mas usa uma média de 25 % mais memória. Isso só terá implicações se o tipo de dados for grandestruct
. Apenas comida para pensar.ToList
ouToArray
começará criando um pequeno buffer. Quando esse buffer é preenchido, ele dobra a capacidade do buffer e continua. Como a capacidade é sempre duplicada, o buffer não utilizado estará sempre entre 0% e 50%.List
eBuffer
verificarãoICollection
; nesse caso, o desempenho será idêntico.(sete anos depois ...)
Algumas outras (boas) respostas se concentraram nas diferenças microscópicas de desempenho que ocorrerão.
Este post é apenas um complemento para mencionar a diferença semântica existente entre a
IEnumerator<T>
produzida por uma matriz (T[]
) em comparação com a retornada por aList<T>
.Melhor ilustrado com o exemplo:
O código acima será executado sem exceção e produz a saída:
Isso mostra que o
IEnumarator<int>
retorno de umint[]
não acompanha se a matriz foi modificada desde a criação do enumerador.Observe que eu declarei a variável local
source
como umIList<int>
. Dessa maneira, asseguro-me de que o compilador C # não otimize aforeach
instrução para algo equivalente a umfor (var idx = 0; idx < source.Length; idx++) { /* ... */ }
loop. Isso é algo que o compilador C # pode fazer se eu usarvar source = ...;
. Na minha versão atual do .NET framework, o enumerador real usado aqui é um tipo de referência não pública,System.SZArrayHelper+SZGenericArrayEnumerator`1[System.Int32]
mas é claro que esse é um detalhe de implementação.Agora, se eu mudar
.ToArray()
para.ToList()
, eu só começar:seguido de uma
System.InvalidOperationException
afirmação explícita:O enumerador subjacente nesse caso é o tipo de valor mutável público
System.Collections.Generic.List`1+Enumerator[System.Int32]
(dentro de umaIEnumerator<int>
caixa nesse caso porque eu usoIList<int>
).Em conclusão, o enumerador produzido por a
List<T>
controla se a lista é alterada durante a enumeração, enquanto o enumerador produzido porT[]
não. Portanto, considere essa diferença ao escolher entre.ToList()
e.ToArray()
.As pessoas costumam adicionar um extra
.ToArray()
ou.ToList()
para contornar uma coleção que controla se ela foi modificada durante a vida útil de um enumerador.(Se alguém quiser saber como a empresa
List<>
controla se a coleção foi modificada, há um campo particular_version
nessa classe que é alterado toda vez que aList<>
atualização é feita.)fonte
Concordo com o @mquander que a diferença de desempenho deve ser insignificante. No entanto, eu queria compará-lo para ter certeza, então fiz - e é insignificante.
Cada matriz / lista de origem tinha 1000 elementos. Assim, você pode ver que as diferenças de tempo e memória são insignificantes.
Minha conclusão: você também pode usar ToList () , uma vez que a
List<T>
fornece mais funcionalidade que uma matriz, a menos que alguns bytes de memória realmente sejam importantes para você.fonte
struct
tipo ou classe grande em vez de primitivo.ToList
ouToArray
e não a enumeração de nenhumaIEnumerable
. List <T> .ToList () ainda cria uma nova lista <T> - não simplesmente "retorna isso".ToArray()
eToList()
diferem muito quando são fornecidos com umICollection<T>
parâmetro - Eles apenas fazem uma única alocação e uma única operação de cópia. AmbosList<T>
eArray
implementarICollection<T>
, para que seus benchmarks não sejam válidos..Select(i => i)
para evitar oICollection<T>
problema de implementação e inclui um grupo de controle para ver quanto tempo é gasto na iteração pela origemIEnumerable<>
.ToList()
geralmente é preferível se você o usarIEnumerable<T>
(no ORM, por exemplo). Se o comprimento da sequência não for conhecido no início,ToArray()
crie uma coleção de comprimento dinâmico como Lista e depois a converta em matriz, o que leva um tempo extra.fonte
Enumerable.ToArray()
chamadasnew Buffer<TSource>(source).ToArray()
. No construtor Buffer, se a origem implementar ICollection, ele chama source.CopyTo (itens, 0) e, em seguida .ToArray () retorna a matriz de itens internos diretamente. Portanto, não há conversão que leve tempo extra nesse caso. Se a fonte não implementar ICollection, o ToArray resultará em uma cópia da matriz, a fim de aparar os locais extras não utilizados do final da matriz, conforme descrito pelo comentário de Scott Rippey acima.A memória sempre será alocada duas vezes - ou algo próximo disso. Como você não pode redimensionar uma matriz, os dois métodos usarão algum tipo de mecanismo para reunir os dados em uma coleção crescente. (Bem, a lista é uma coleção crescente em si mesma.)
A lista usa uma matriz como armazenamento interno e dobra a capacidade quando necessário. Isso significa que, em média, 2/3 dos itens foram realocados pelo menos uma vez, metade dos que foram realocados pelo menos duas vezes, metade dos que pelo menos três vezes e assim por diante. Isso significa que cada item foi realocado em média 1,3 vezes, o que não representa muita sobrecarga.
Lembre-se também de que, se você estiver coletando cadeias, a própria coleção conterá apenas as referências às cadeias, as próprias cadeias não serão realocadas.
fonte
É 2020 lá fora e todo mundo está usando o .NET Core 3.1, então decidi executar alguns benchmarks com o Benchmark.NET.
TL; DR: ToArray () é melhor em termos de desempenho e faz um trabalho melhor ao transmitir intenções, se você não planeja alterar a coleção.
Os resultados são:
fonte
ToImmutableArray()
(do pacote System.Collections.Immutable) 😉Editar : a última parte desta resposta não é válida. No entanto, o restante ainda é uma informação útil, então deixarei.
Sei que este é um post antigo, mas depois de ter a mesma pergunta e fazer algumas pesquisas, encontrei algo interessante que vale a pena compartilhar.
Primeiro, concordo com o @mquander e sua resposta. Ele está certo ao dizer que, em termos de desempenho, os dois são idênticos.
No entanto, tenho usado o Reflector para examinar os métodos no
System.Linq.Enumerable
espaço de nomes das extensões e notei uma otimização muito comum.Sempre que possível, a
IEnumerable<T>
fonte é convertida paraIList<T>
ouICollection<T>
para otimizar o método. Por exemplo, olheElementAt(int)
.Curiosamente, a Microsoft optou por otimizar apenas
IList<T>
, mas nãoIList
. Parece que a Microsoft prefere usar aIList<T>
interface.System.Array
implementa apenasIList
, portanto, não se beneficiará de nenhuma dessas otimizações de extensão.Portanto, afirmo que a melhor prática é usar o método
.ToList()
métodoSe você usar qualquer um dos métodos de extensão ou passar a lista para outro método, é possível que ele seja otimizado para um
IList<T>
.fonte
Eu encontrei os outros benchmarks que as pessoas fizeram aqui faltando, então aqui está a minha falha. Deixe-me saber se você encontrar algo errado com a minha metodologia.
Você pode baixar o script LINQPad aqui .
Resultados:
Ajustando o código acima, você descobrirá que:
int
s em vez destring
s.struct
s grandes em vez destring
s geralmente leva muito mais tempo, mas na verdade não altera muito a proporção.Isso concorda com as conclusões das respostas mais votadas:
ToList()
é executado de forma consistente com mais rapidez e seria uma escolha melhor se você não planeja manter os resultados por um longo tempo.Atualizar
A @JonHanna apontou que, dependendo da implementação
Select
, é possível que umaToList()
ouToArray()
implementação preveja o tamanho da coleção resultante com antecedência. A substituição.Select(i => i)
no código acima porWhere(i => true)
resultados muito semelhantes no momento e é mais provável que isso seja feito independentemente da implementação do .NET.fonte
100000
e os usarão para otimizar os doisToList()
eToArray()
,ToArray()
sendo um pouco mais leves, porque não precisam da operação de redução necessária. caso contrário, qual é o único localToList()
tem a vantagem. O exemplo na pergunta ainda perderia, porque osWhere
meios que a previsão de tamanho não pode ser feita..Select(i => i)
poderia ser substituído por.Where(i => true)
para corrigir isso.ToArray()
uma vantagem) e um que não é, como acima, e comparar os resultados.ToArray()
ainda perde no melhor cenário. ComMath.Pow(2, 15)
elementos, é (ToList: 700ms, ToArray: 900ms). A adição de mais um elemento o leva a (ToList: 925, ToArray: 1350). Gostaria de saber seToArray
ainda está copiando a matriz, mesmo quando já é o tamanho perfeito? Eles provavelmente pensaram que era uma ocorrência rara o suficiente para que não valesse a pena o condicional extra.Você deve basear sua decisão de escolher
ToList
ou comToArray
base no que é ideal a escolha do design. Se você deseja uma coleção que só pode ser iterada e acessada por índice, escolhaToArray
. Se você quiser recursos adicionais para adicionar e remover da coleção mais tarde, sem muito aborrecimento, faça umToList
(não é realmente que você não possa adicionar a uma matriz, mas essa não é a ferramenta correta para isso normalmente).Se o desempenho for importante, considere também o que seria mais rápido de operar. Realisticamente, você não telefonará
ToList
ouToArray
um milhão de vezes, mas poderá trabalhar na coleção obtida um milhão de vezes. Nesse aspecto[]
é melhor, poisList<>
é[]
com alguma sobrecarga. Veja este tópico para obter algumas comparações de eficiência: Qual é mais eficiente: List <int> ou int []Nos meus próprios testes, há um tempo atrás, eu encontrei
ToArray
mais rápido. E não tenho certeza de quão distorcidos os testes foram. A diferença de desempenho é tão insignificante, que pode ser notada apenas se você estiver executando essas consultas em um loop milhões de vezes.fonte
Uma resposta muito tardia, mas acho que será útil para os googlers.
Ambos são péssimos quando criaram usando linq. Ambos implementam o mesmo código para redimensionar o buffer, se necessário .
ToArray
usa internamente uma classe para converterIEnumerable<>
em matriz, alocando uma matriz de 4 elementos. Se isso não for suficiente, ele dobrará o tamanho criando uma nova matriz, dobrando o tamanho da corrente e copiando a matriz atual para ela. No final, ele aloca uma nova matriz de contagem de seus itens. Se sua consulta retornar 129 elementos, o ToArray fará 6 alocações e operações de cópia de memória para criar uma matriz de 256 elementos e, em seguida, uma outra matriz de 129 para retornar. muito pela eficiência da memória.A ToList faz a mesma coisa, mas ignora a última alocação, pois você pode adicionar itens no futuro. A lista não se importa se é criada a partir de uma consulta linq ou criada manualmente.
para criação A lista é melhor com memória, mas pior com a CPU, uma vez que a lista é uma solução genérica. Toda ação requer verificações de alcance adicionais às verificações internas de arrays do .net.
Portanto, se você repetir o conjunto de resultados muitas vezes, as matrizes serão boas, pois significam menos verificações de intervalo do que as listas, e os compiladores geralmente otimizam as matrizes para acesso seqüencial.
A alocação de inicialização da lista pode ser melhor se você especificar o parâmetro de capacidade ao criá-lo. Nesse caso, ele alocará a matriz apenas uma vez, supondo que você saiba o tamanho do resultado.
ToList
O linq não especifica uma sobrecarga para fornecê-lo, portanto, precisamos criar nosso método de extensão que cria uma lista com a capacidade especificada e depois usaList<>.AddRange
.Para finalizar esta resposta, tenho que escrever as seguintes frases
fonte
List<T>
, mas quando não o faz ou quando não pode, não pode ajudá-lo.Essa é uma pergunta antiga - mas para o benefício dos usuários que a encontram, há também uma alternativa de 'Memoizar' o Enumerável - que tem o efeito de armazenar em cache e interromper a enumeração múltipla de uma instrução Linq, que é o que ToArray () e ToList () são usados muito, mesmo que os atributos de coleção da lista ou matriz nunca sejam usados.
Memoize está disponível na RX / System.Interactive lib e é explicado aqui: Mais LINQ com System.Interactive
( No blog de Bart De'Smet, que é uma leitura altamente recomendada se você estiver trabalhando muito com o Linq to Objects)
fonte
Uma opção é adicionar seu próprio método de extensão que retorna apenas uma leitura
ICollection<T>
. Isso pode ser melhor do que usarToList
ouToArray
quando você não deseja usar as propriedades de indexação de uma matriz / lista ou adicionar / remover de uma lista.Testes unitários:
fonte
ToListAsync<T>()
é preferível.No Entity Framework 6, ambos os métodos chamam o mesmo método interno, mas
ToArrayAsync<T>()
chamalist.ToArray()
no final, que é implementado comoO mesmo ocorre
ToArrayAsync<T>()
com algumas despesas gerais, portantoToListAsync<T>()
é preferido.fonte
Pergunta antiga, mas novos questionadores o tempo todo.
De acordo com a fonte do System.Linq.Enumerable ,
ToList
basta retornar anew List(source)
, enquantoToArray
usa anew Buffer<T>(source).ToArray()
para retornar aT[]
.Enquanto estiver executando em um
IEnumerable<T>
único objeto,ToArray
aloque memória mais uma vez queToList
. Mas você não precisa se preocupar com isso na maioria dos casos, porque o GC fará a coleta de lixo quando necessário.Aqueles que estão questionando essa pergunta podem executar o código a seguir em sua própria máquina e você receberá sua resposta.
Eu obtive estes resultados na minha máquina:
Devido ao limite do stackoverflow na quantidade de caracteres da resposta, as listas de amostra do Grupo2 e do Grupo3 são omitidas.
Como você pode ver, não é realmente importante usar
ToList
ouToArry
na maioria dos casos.Ao processar
IEnumerable<T>
objetos calculados em tempo de execução , se a carga trazida pelo cálculo for pesada que as operações de alocação e cópia de memória deToList
eToArray
, a disparidade será insignificante (C.ToList vs C.ToArray
eS.ToList vs S.ToArray
).A diferença pode ser observada apenas em
IEnumerable<T>
objetos calculados sem tempo de execução (C1.ToList vs C1.ToArray
eS1.ToList vs S1.ToArray
). Mas a diferença absoluta (<60ms) ainda é aceitável em um milhão de objetos pequenosIEnumerable<T>
. De fato, a diferença é decidida pela implementaçãoEnumerator<T>
deIEnumerable<T>
. Portanto, se seu programa é realmente muito sensível a isso, você precisa criar um perfil, perfil, perfil ! Por fim, você provavelmente descobrirá que o gargalo não está ativadoToList
ouToArray
, mas sim os detalhes dos enumeradores.E, o resultado
C2.ToList vs C2.ToArray
eS2.ToList vs S2.ToArray
mostra que você realmente não precisa se preocuparToList
ouToArray
calculado sem tempo de execuçãoICollection<T>
objetos .Obviamente, são apenas resultados na minha máquina, o tempo real gasto com essas operações em máquinas diferentes não será o mesmo, você pode descobrir na sua máquina usando o código acima.
A única razão pela qual você precisa fazer uma escolha é que você tem necessidades específicas
List<T>
ouT[]
, conforme descrito pela resposta de @Jeppe Stig Nielsen .fonte
Para qualquer pessoa interessada em usar esse resultado em outro Linq-to-sql, como
o SQL gerado será o mesmo, independentemente de você ter usado uma Lista ou Matriz para o myListOrArray. Agora eu sei que alguns podem perguntar por que até enumerar antes dessa declaração, mas há uma diferença entre o SQL gerado a partir de um IQueryable vs (List ou Array).
fonte