Desempenho de matrizes x listas


191

Digamos que você precise ter uma lista / matriz de números inteiros que você precisa repetir com frequência, e eu quero dizer com muita frequência. Os motivos podem variar, mas digamos que esteja no coração do loop mais interno de um processamento de alto volume.

Em geral, optamos pelo uso de Listas (Lista) devido à sua flexibilidade em tamanho. Além disso, a documentação do msdn afirma que as Lists usam uma matriz internamente e devem ter o mesmo desempenho rápido (uma rápida olhada no Reflector confirma isso). No entanto, há alguma sobrecarga envolvida.

Alguém realmente mediu isso? iterar 6 milhões de vezes através de uma lista levaria o mesmo tempo que uma matriz faria?


3
Questões de desempenho à parte, prefiro o uso de Matrizes sobre Listas para seu tamanho fixo (nos casos em que a alteração do número de itens não é necessária, é claro). Ao ler o código existente, acho útil saber rapidamente que um item é forçado a ter tamanho fixo, em vez de precisar inspecionar o código mais abaixo na função.
22814 Warren Stevens

2
T[]vs. List<T>pode fazer uma grande diferença de desempenho. Acabei de otimizar um aplicativo intensivo de loop extremamente (aninhado) para passar de listas para matrizes no .NET 4.0. Eu estava esperando talvez 5% a 10% de melhoria, mas obtive mais de 40% de aceleração! Não há outras alterações além de mover diretamente da lista para a matriz. Todas as enumerações foram feitas com foreachinstruções. Com base na resposta de Marc Gravell, parece que foreachcom List<T>é particularmente ruim.
Special Sauce

Respostas:


221

Muito fácil de medir ...

Em um pequeno número de código de processamento em circuito fechado, onde eu sei que o comprimento é fixo, eu uso matrizes para esse pequeno pouquinho extra de otimização; matrizes podem ser marginalmente mais rápidas se você usar o indexador / para formulário - mas o IIRC acredita que depende do tipo de dados na matriz. Mas, a menos que você precise otimizar, mantenha-o simples e use List<T>etc.

Obviamente, isso só se aplica se você estiver lendo todos os dados; um dicionário seria mais rápido para pesquisas baseadas em chaves.

Aqui estão meus resultados usando "int" (o segundo número é uma soma de verificação para verificar se todos fizeram o mesmo trabalho):

(editado para corrigir o erro)

List/for: 1971ms (589725196)
Array/for: 1864ms (589725196)
List/foreach: 3054ms (589725196)
Array/foreach: 1860ms (589725196)

com base no equipamento de teste:

using System;
using System.Collections.Generic;
using System.Diagnostics;
static class Program
{
    static void Main()
    {
        List<int> list = new List<int>(6000000);
        Random rand = new Random(12345);
        for (int i = 0; i < 6000000; i++)
        {
            list.Add(rand.Next(5000));
        }
        int[] arr = list.ToArray();

        int chk = 0;
        Stopwatch watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            int len = list.Count;
            for (int i = 0; i < len; i++)
            {
                chk += list[i];
            }
        }
        watch.Stop();
        Console.WriteLine("List/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            for (int i = 0; i < arr.Length; i++)
            {
                chk += arr[i];
            }
        }
        watch.Stop();
        Console.WriteLine("Array/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in list)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("List/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in arr)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("Array/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        Console.ReadLine();
    }
}

8
Detalhe interessante: Aqui estão os horários de RELEASE / DEBUG no meu equipamento (.net 3.5 sp1): 0,92, 0,80, 0,96, 0,93; o que me diz que há alguma inteligência na VM para otimizar o Array / for loop aproximadamente 10% melhor que os outros casos.
David Schmitt

2
Sim, há otimização JIT para array / for. Na verdade, eu tive a impressão de que ele incluía o caso Length (já que ele sabe que está consertado), por isso não o tirei primeiro (ao contrário de List onde eu fiz). Obrigado pela atualização.
Marc Gravell

2
Esquisito. Meus testes muito semelhantes não mostram diferença significativa entre for e foreach ao usar matrizes. Vai investigar minuciosamente em um post com um benchmark que outras pessoas podem correr e enviar resultados para ...
Jon Skeet

1
Eu obtenho resultados dramaticamente diferentes se usar uma variável diferente para chk para cada teste (chk1, chk2, chk3, chk4). Listar / para = 1303ms, Matriz / para = 433ms. Alguma idéia do porquê?
31413 Jon

4
O link mencionado no comentário acima de Jon para o blog de Jon Skeet foi quebrado. Abaixo está o link atualizado. codeblog.jonskeet.uk/2009/01/29/…
Josh DeLong

88

Resumo:

  • A matriz precisa usar:

    • Tão frequentemente quanto possível. É rápido e utiliza o menor intervalo de RAM para obter informações sobre a mesma quantidade.
    • Se você souber a contagem exata de células necessárias
    • Se os dados salvos na matriz <85000 b (85000/32 = 2656 elementos para dados inteiros)
    • Se necessário, alta velocidade de acesso aleatório
  • A lista precisa usar:

    • Se necessário, adicione células ao final da lista (geralmente)
    • Se necessário, adicione células no começo / meio da lista (NÃO É MUITO)
    • Se os dados salvos na matriz <85000 b (85000/32 = 2656 elementos para dados inteiros)
    • Se necessário, alta velocidade de acesso aleatório
  • O LinkedList precisa usar:

    • Se necessário, adicione células no começo / meio / fim da lista (frequentemente)
    • Se necessário, apenas acesso seqüencial (frente / trás)
    • Se você precisar salvar itens GRANDES, mas a contagem de itens é baixa.
    • Melhor não usar para uma grande quantidade de itens, pois ele usa memória adicional para links.

Mais detalhes:

significado da cor

Matriz x Lista x Lista Vinculada

Muito mais detalhes:

https://stackoverflow.com/a/29263914/4423545


Estou um pouco confuso com a sua afirmação de que o prepend da lista é relativamente rápido, mas a inserção é lenta. A inserção também é linear, e é mais rápida em 50%, em média, do que precede.
Mike Marynowski 18/09/19

1
@MikeMarynowski na lista de c # é wrapper em torno de Array. Portanto, no caso de inserção na lista, você terá tempo linear apenas para algum ponto. Após esse sistema, criará uma nova matriz maior e precisará de tempo para copiar itens da antiga.
Andrew Andrew

A mesma coisa com prepends.
Mike Marynowski 18/09/19

Uma operação de pré-adição é apenas uma inserção em 0. É a pior inserção de casos em termos de desempenho; portanto, se a inserção for lenta, a pré-inserção será ainda mais lenta.
Mike Marynowski 18/09/19

Tanto a inserção como a pré-adição são O (n) (amortizadas). Um prefixo é uma inserção, mas é a inserção mais lenta possível, pois precisa mover TODOS os itens da lista um ponto para cima. Uma inserção em um local aleatório só precisa mover os itens que estão em um índice mais alto que o ponto de inserção, portanto, 50% dos itens em média.
Mike Marynowski 18/09/19

26

Eu acho que o desempenho será bem parecido. A sobrecarga envolvida ao usar uma lista versus uma matriz é IMHO quando você adiciona itens à lista e quando a lista precisa aumentar o tamanho da matriz que ela está usando internamente, quando a capacidade da matriz é atingida.

Suponha que você tenha uma lista com capacidade de 10; a lista aumentará sua capacidade assim que você desejar adicionar o 11º elemento. Você pode diminuir o impacto no desempenho, inicializando a Capacidade da lista com o número de itens que ela conterá.

Mas, para descobrir se a iteração sobre uma lista é tão rápida quanto a iteração sobre uma matriz, por que você não a avalia?

int numberOfElements = 6000000;

List<int> theList = new List<int> (numberOfElements);
int[] theArray = new int[numberOfElements];

for( int i = 0; i < numberOfElements; i++ )
{
    theList.Add (i);
    theArray[i] = i;
}

Stopwatch chrono = new Stopwatch ();

chrono.Start ();

int j;

 for( int i = 0; i < numberOfElements; i++ )
 {
     j = theList[i];
 }

 chrono.Stop ();
 Console.WriteLine (String.Format("iterating the List took {0} msec", chrono.ElapsedMilliseconds));

 chrono.Reset();

 chrono.Start();

 for( int i = 0; i < numberOfElements; i++ )
 {
     j = theArray[i];
 }

 chrono.Stop ();
 Console.WriteLine (String.Format("iterating the array took {0} msec", chrono.ElapsedMilliseconds));

 Console.ReadLine();

No meu sistema; a iteração na matriz levou 33 ms; iterar sobre a lista levou 66 ms.

Para ser sincero, não esperava que a variação fosse tão grande. Então, eu coloquei minha iteração em um loop: agora, eu executo as duas iterações 1000 vezes. Os resultados são:

iterar a lista levou 67146 mseg iterar a matriz levou 40821 mseg

Agora, a variação não é mais tão grande, mas ainda assim ...

Portanto, iniciei o .NET Reflector e o getter do indexador da classe List se parece com o seguinte:

public T get_Item(int index)
{
    if (index >= this._size)
    {
        ThrowHelper.ThrowArgumentOutOfRangeException();
    }
    return this._items[index];
}

Como você pode ver, quando você usa o indexador da lista, a lista executa uma verificação se você não está saindo dos limites da matriz interna. Essa verificação adicional tem um custo.


Oi Frederik, Obrigado! Como você explicaria que sua lista levou o dobro do tempo das matrizes. Não é o que você esperaria. Você tentou aumentar o número de elementos?
Boaz

1
Não retornaria isso._items [index]; já lança uma exceção se o índice estava fora da faixa? Por que o .NET tem essa verificação extra quando o resultado final é o mesmo, com ou sem?
John Mercier

@ John Mercier, a verificação é contra o tamanho da lista (número de itens atualmente contidos), que é diferente e provavelmente menor que a capacidade da matriz _items. A matriz é alocada com excesso de capacidade para agilizar a adição de itens futuros, sem a necessidade de realocação para cada adição.
Trasvi

21

se você está obtendo um único valor de qualquer um (não em um loop), os dois fazem a verificação de limites (você lembra do código gerenciado); é apenas a lista que faz isso duas vezes. Veja as notas mais tarde para saber por que isso provavelmente não é grande coisa.

Se você estiver usando o seu próprio para (int int i = 0; i <x. [Comprimento / Contagem]; i ++), a diferença principal é a seguinte:

  • Matriz:
    • verificação de limites é removida
  • Listas
    • verificação de limites é realizada

Se você estiver usando o foreach, a principal diferença é a seguinte:

  • Matriz:
    • nenhum objeto está alocado para gerenciar a iteração
    • verificação de limites é removida
  • Listar através de uma variável conhecida por List.
    • a variável de gerenciamento de iteração é alocada por pilha
    • verificação de limites é realizada
  • Listar através de uma variável conhecida como IList.
    • a variável de gerenciamento de iteração é alocada para heap
    • A verificação de limites também é realizada. Os valores das listas não podem ser alterados durante o foreach, enquanto os da matriz podem ser.

A verificação de limites geralmente não é grande coisa (especialmente se você estiver em uma CPU com uma previsão profunda de canal e ramificação - a norma para a maioria dos dias de hoje), mas apenas seu próprio perfil pode dizer se isso é um problema. Se você estiver em partes do código em que está evitando alocações de heap (bons exemplos são bibliotecas ou implementações de código de hash), garanta que a variável seja digitada, pois List not IList evitará essa armadilha. Como sempre perfil, se isso importa.


11

[ Veja também esta pergunta ]

Modifiquei a resposta de Marc para usar números aleatórios reais e, na verdade, fazer o mesmo trabalho em todos os casos.

Resultados:

         para foreach
Matriz: 1575ms 1575ms (+ 0%)
Lista: 1630ms 2627ms (+ 61%)
         (+ 3%) (+ 67%)

(Soma de verificação: -1000038876)

Compilado como versão no VS 2008 SP1. Executando sem depuração em um Q6600@2.40GHz, .NET 3.5 SP1.

Código:

class Program
{
    static void Main(string[] args)
    {
        List<int> list = new List<int>(6000000);
        Random rand = new Random(1);
        for (int i = 0; i < 6000000; i++)
        {
            list.Add(rand.Next());
        }
        int[] arr = list.ToArray();

        int chk = 0;
        Stopwatch watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            int len = list.Count;
            for (int i = 0; i < len; i++)
            {
                chk += list[i];
            }
        }
        watch.Stop();
        Console.WriteLine("List/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            int len = arr.Length;
            for (int i = 0; i < len; i++)
            {
                chk += arr[i];
            }
        }
        watch.Stop();
        Console.WriteLine("Array/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in list)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("List/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in arr)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("Array/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);
        Console.WriteLine();

        Console.ReadLine();
    }
}

Isso é estranho - acabei de executar seu código exato, criado a partir da linha de comando (3.5SP1) com / o + / debug- e meus resultados são: list / for: 1524; matriz / para: 1472; lista / foreach: 4128; array / foreach: 1484.
Jon Skeet

Você diz que isso foi compilado como versão - posso apenas verificar se você o executou em vez de depurá-lo? Pergunta boba, eu sei, mas não posso explicar os resultados de outra forma ...
Jon Skeet

2

As medidas são boas, mas você obterá resultados significativamente diferentes, dependendo do que estiver fazendo exatamente no seu loop interno. Meça sua própria situação. Se você estiver usando multiencadeamento, isso por si só é uma atividade não trivial.


2

De fato, se você executar alguns cálculos complexos dentro do loop, o desempenho do indexador de matriz versus o indexador de lista pode ser tão marginalmente pequeno que, eventualmente, isso não importa.


2

Aqui está um que usa Dictionaries, IEnumerable:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

static class Program
{
    static void Main()
    {
        List<int> list = new List<int>(6000000);

        for (int i = 0; i < 6000000; i++)
        {
                list.Add(i);
        }
        Console.WriteLine("Count: {0}", list.Count);

        int[] arr = list.ToArray();
        IEnumerable<int> Ienumerable = list.ToArray();
        Dictionary<int, bool> dict = list.ToDictionary(x => x, y => true);

        int chk = 0;
        Stopwatch watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            int len = list.Count;
            for (int i = 0; i < len; i++)
            {
                chk += list[i];
            }
        }
        watch.Stop();
        Console.WriteLine("List/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            for (int i = 0; i < arr.Length; i++)
            {
                chk += arr[i];
            }
        }
        watch.Stop();
        Console.WriteLine("Array/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in Ienumerable)
            {
                chk += i;
            }
        }

        Console.WriteLine("Ienumerable/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in dict.Keys)
            {
                chk += i;
            }
        }

        Console.WriteLine("Dict/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);


        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in list)
            {
                chk += i;
            }
        }

        watch.Stop();
        Console.WriteLine("List/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in arr)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("Array/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);



        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in Ienumerable)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("Ienumerable/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in dict.Keys)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("Dict/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        Console.ReadLine();
    }
}

2

Não tente adicionar capacidade aumentando o número de elementos.

atuação

List For Add: 1ms
Array For Add: 2397ms

    Stopwatch watch;
        #region --> List For Add <--

        List<int> intList = new List<int>();
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 60000; rpt++)
        {
            intList.Add(rand.Next());
        }
        watch.Stop();
        Console.WriteLine("List For Add: {0}ms", watch.ElapsedMilliseconds);
        #endregion

        #region --> Array For Add <--

        int[] intArray = new int[0];
        watch = Stopwatch.StartNew();
        int sira = 0;
        for (int rpt = 0; rpt < 60000; rpt++)
        {
            sira += 1;
            Array.Resize(ref intArray, intArray.Length + 1);
            intArray[rpt] = rand.Next();

        }
        watch.Stop();
        Console.WriteLine("Array For Add: {0}ms", watch.ElapsedMilliseconds);

        #endregion

O redimensionamento de uma matriz 60k vezes será lento ... Certamente, no uso no mundo real, a abordagem seria apenas verificar quantos slots extras você precisa, redimensioná-la para + 60k e depois percorrer as inserções.
precisa saber é o seguinte

O redimensionamento de uma matriz é muito rápido se você dobrar o tamanho sempre que achar que precisa de mais espaço. Descobri que parece levar o mesmo tempo fazendo isso, pois apenas redimensiona uma vez após a declaração inicial. Isso oferece a flexibilidade de uma lista e a maior parte da velocidade de uma matriz.
user1318499

2

Eu estava preocupado que os Benchmarks publicados em outras respostas ainda deixassem espaço para o compilador otimizar, eliminar ou mesclar loops, então escrevi um que:

  • Entradas imprevisíveis usadas (aleatórias)
  • Executa um cálculo com o resultado impresso no console
  • Modifica os dados de entrada a cada repetição

O resultado é que uma matriz direta tem desempenho 250% melhor que o acesso a uma matriz envolvida em um IList:

  • 1 bilhão de acessos de array: 4000 ms
  • 1 bilhão de acessos à lista: 10000 ms
  • 100 milhões de acessos de array: 350 ms
  • 100 milhões de acessos à lista: 1000 ms

Aqui está o código:

static void Main(string[] args) {
  const int TestPointCount = 1000000;
  const int RepetitionCount = 1000;

  Stopwatch arrayTimer = new Stopwatch();
  Stopwatch listTimer = new Stopwatch();

  Point2[] points = new Point2[TestPointCount];
  var random = new Random();
  for (int index = 0; index < TestPointCount; ++index) {
    points[index].X = random.NextDouble();
    points[index].Y = random.NextDouble();
  }

  for (int repetition = 0; repetition <= RepetitionCount; ++repetition) {
    if (repetition > 0) { // first repetition is for cache warmup
      arrayTimer.Start();
    }
    doWorkOnArray(points);
    if (repetition > 0) { // first repetition is for cache warmup
      arrayTimer.Stop();
    }

    if (repetition > 0) { // first repetition is for cache warmup
      listTimer.Start();
    }
    doWorkOnList(points);
    if (repetition > 0) { // first repetition is for cache warmup
      listTimer.Stop();
    }
  }

  Console.WriteLine("Ignore this: " + points[0].X + points[0].Y);
  Console.WriteLine(
    string.Format(
      "{0} accesses on array took {1} ms",
      RepetitionCount * TestPointCount, arrayTimer.ElapsedMilliseconds
    )
  );
  Console.WriteLine(
    string.Format(
      "{0} accesses on list took {1} ms",
      RepetitionCount * TestPointCount, listTimer.ElapsedMilliseconds
    )
  );

}

private static void doWorkOnArray(Point2[] points) {
  var random = new Random();

  int pointCount = points.Length;

  Point2 accumulated = Point2.Zero;
  for (int index = 0; index < pointCount; ++index) {
    accumulated.X += points[index].X;
    accumulated.Y += points[index].Y;
  }

  accumulated /= pointCount;

  // make use of the result somewhere so the optimizer can't eliminate the loop
  // also modify the input collection so the optimizer can merge the repetition loop
  points[random.Next(0, pointCount)] = accumulated;
}

private static void doWorkOnList(IList<Point2> points) {
  var random = new Random();

  int pointCount = points.Count;

  Point2 accumulated = Point2.Zero;
  for (int index = 0; index < pointCount; ++index) {
    accumulated.X += points[index].X;
    accumulated.Y += points[index].Y;
  }

  accumulated /= pointCount;

  // make use of the result somewhere so the optimizer can't eliminate the loop
  // also modify the input collection so the optimizer can merge the repetition loop
  points[random.Next(0, pointCount)] = accumulated;
}

0

Como a List <> usa matrizes internamente, o desempenho básico deve ser o mesmo. Por duas razões, a lista pode ser um pouco mais lenta:

  • Para procurar um elemento na lista, é chamado um método de Lista, que faz a busca na matriz subjacente. Então, você precisa de uma chamada de método adicional lá. Por outro lado, o compilador pode reconhecer isso e otimizar a chamada "desnecessária".
  • O compilador pode fazer algumas otimizações especiais se souber o tamanho da matriz, o que não pode ser feito para uma lista de tamanho desconhecido. Isso pode trazer alguma melhoria de desempenho se você tiver apenas alguns elementos em sua lista.

Para verificar se isso faz alguma diferença para você, provavelmente é melhor ajustar as funções de tempo publicadas para uma lista do tamanho que você planeja usar e ver como são os resultados para o seu caso especial.


0

Como eu tinha uma pergunta semelhante, isso me deu um começo rápido.

Minha pergunta é um pouco mais específica, 'qual é o método mais rápido para uma implementação de matriz reflexiva'

Os testes feitos por Marc Gravell mostram muito, mas não exatamente o tempo de acesso. Seu tempo inclui também o loop sobre as listas e matrizes. Como também criei um terceiro método que queria testar, um 'Dicionário', apenas para comparar, estendi o código do teste hist.

Primeiramente, faço um teste usando uma constante, o que me dá um certo tempo, incluindo o loop. Esse é um momento "vazio", excluindo o acesso real. Então eu faço um teste para acessar a estrutura do assunto, isso me dá um tempo, um loop e o acesso real 'overhead included'.

A diferença entre o tempo 'bare' e o tempo 'overhead indluded' me dá uma indicação do tempo de 'acesso à estrutura'.

Mas quão preciso é esse momento? Durante o teste, as janelas farão algum tempo cortando shure. Não tenho informações sobre o tempo de redução, mas presumo que ele seja distribuído igualmente durante o teste e na ordem de dezenas de ms, o que significa que a precisão do tempo deve ser da ordem de +/- 100 ms ou mais. Uma estimativa um pouco grosseira? De qualquer forma, uma fonte de um erro sistemático de medição.

Além disso, os testes foram feitos no modo 'Debug' sem otimização. Caso contrário, o compilador pode alterar o código de teste real.

Portanto, obtenho dois resultados, um para uma constante, marcado '(c)', e outro para o acesso marcado como '(n)' e a diferença 'dt' me diz quanto tempo leva o acesso real.

E estes são os resultados:

          Dictionary(c)/for: 1205ms (600000000)
          Dictionary(n)/for: 8046ms (589725196)
 dt = 6841

                List(c)/for: 1186ms (1189725196)
                List(n)/for: 2475ms (1779450392)
 dt = 1289

               Array(c)/for: 1019ms (600000000)
               Array(n)/for: 1266ms (589725196)
 dt = 247

 Dictionary[key](c)/foreach: 2738ms (600000000)
 Dictionary[key](n)/foreach: 10017ms (589725196)
 dt = 7279

            List(c)/foreach: 2480ms (600000000)
            List(n)/foreach: 2658ms (589725196)
 dt = 178

           Array(c)/foreach: 1300ms (600000000)
           Array(n)/foreach: 1592ms (589725196)
 dt = 292


 dt +/-.1 sec   for    foreach
 Dictionary     6.8       7.3
 List           1.3       0.2
 Array          0.2       0.3

 Same test, different system:
 dt +/- .1 sec  for    foreach
 Dictionary     14.4   12.0
       List      1.7    0.1
      Array      0.5    0.7

Com melhores estimativas dos erros de temporização (como remover o erro sistemático de medição devido à redução do tempo?), Mais poderia ser dito sobre os resultados.

Parece que List / foreach tem o acesso mais rápido, mas a sobrecarga está acabando.

A diferença entre List / for e List / foreach é estranha. Talvez algum dinheiro esteja envolvido?

Além disso, para acessar uma matriz, não importa se você usa um forloop ou um foreachloop. Os resultados de tempo e sua precisão tornam os resultados 'comparáveis'.

Usar um dicionário é de longe o mais lento, só o considerei, porque no lado esquerdo (o indexador) tenho uma lista esparsa de números inteiros e não um intervalo, como é usado nesses testes.

Aqui está o código de teste modificado.

Dictionary<int, int> dict = new Dictionary<int, int>(6000000);
List<int> list = new List<int>(6000000);
Random rand = new Random(12345);
for (int i = 0; i < 6000000; i++)
{
    int n = rand.Next(5000);
    dict.Add(i, n);
    list.Add(n);
}
int[] arr = list.ToArray();

int chk = 0;
Stopwatch watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    int len = dict.Count;
    for (int i = 0; i < len; i++)
    {
        chk += 1; // dict[i];
    }
}
watch.Stop();
long c_dt = watch.ElapsedMilliseconds;
Console.WriteLine("         Dictionary(c)/for: {0}ms ({1})", c_dt, chk);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    int len = dict.Count;
    for (int i = 0; i < len; i++)
    {
        chk += dict[i];
    }
}
watch.Stop();
long n_dt = watch.ElapsedMilliseconds;
Console.WriteLine("         Dictionary(n)/for: {0}ms ({1})", n_dt, chk);
Console.WriteLine("dt = {0}", n_dt - c_dt);

watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    int len = list.Count;
    for (int i = 0; i < len; i++)
    {
        chk += 1; // list[i];
    }
}
watch.Stop();
c_dt = watch.ElapsedMilliseconds;
Console.WriteLine("               List(c)/for: {0}ms ({1})", c_dt, chk);

watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    int len = list.Count;
    for (int i = 0; i < len; i++)
    {
        chk += list[i];
    }
}
watch.Stop();
n_dt = watch.ElapsedMilliseconds;
Console.WriteLine("               List(n)/for: {0}ms ({1})", n_dt, chk);
Console.WriteLine("dt = {0}", n_dt - c_dt);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    for (int i = 0; i < arr.Length; i++)
    {
        chk += 1; // arr[i];
    }
}
watch.Stop();
c_dt = watch.ElapsedMilliseconds;
Console.WriteLine("              Array(c)/for: {0}ms ({1})", c_dt, chk);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    for (int i = 0; i < arr.Length; i++)
    {
        chk += arr[i];
    }
}
watch.Stop();
n_dt = watch.ElapsedMilliseconds;
Console.WriteLine("Array(n)/for: {0}ms ({1})", n_dt, chk);
Console.WriteLine("dt = {0}", n_dt - c_dt);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    foreach (int i in dict.Keys)
    {
        chk += 1; // dict[i]; ;
    }
}
watch.Stop();
c_dt = watch.ElapsedMilliseconds;
Console.WriteLine("Dictionary[key](c)/foreach: {0}ms ({1})", c_dt, chk);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    foreach (int i in dict.Keys)
    {
        chk += dict[i]; ;
    }
}
watch.Stop();
n_dt = watch.ElapsedMilliseconds;
Console.WriteLine("Dictionary[key](n)/foreach: {0}ms ({1})", n_dt, chk);
Console.WriteLine("dt = {0}", n_dt - c_dt);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    foreach (int i in list)
    {
        chk += 1; // i;
    }
}
watch.Stop();
c_dt = watch.ElapsedMilliseconds;
Console.WriteLine("           List(c)/foreach: {0}ms ({1})", c_dt, chk);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    foreach (int i in list)
    {
        chk += i;
    }
}
watch.Stop();
n_dt = watch.ElapsedMilliseconds;
Console.WriteLine("           List(n)/foreach: {0}ms ({1})", n_dt, chk);
Console.WriteLine("dt = {0}", n_dt - c_dt);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    foreach (int i in arr)
    {
        chk += 1; // i;
    }
}
watch.Stop();
c_dt = watch.ElapsedMilliseconds;
Console.WriteLine("          Array(c)/foreach: {0}ms ({1})", c_dt, chk);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    foreach (int i in arr)
    {
        chk += i;
    }
}
watch.Stop();
n_dt = watch.ElapsedMilliseconds;
Console.WriteLine("Array(n)/foreach: {0}ms ({1})", n_dt, chk);
Console.WriteLine("dt = {0}", n_dt - c_dt);

0

Em alguns breves testes, achei uma combinação dos dois melhor no que eu chamaria de matemática razoavelmente intensiva:

Tipo: List<double[]>

Hora: 00: 00: 05.1861300

Tipo: List<List<double>>

Hora: 00: 00: 05.7941351

Tipo: double[rows * columns]

Hora: 00: 00: 06.0547118

Executando o código:

int rows = 10000;
int columns = 10000;

IMatrix Matrix = new IMatrix(rows, columns);

Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();


for (int r = 0; r < Matrix.Rows; r++)
    for (int c = 0; c < Matrix.Columns; c++)
        Matrix[r, c] = Math.E;

for (int r = 0; r < Matrix.Rows; r++)
    for (int c = 0; c < Matrix.Columns; c++)
        Matrix[r, c] *= -Math.Log(Math.E);


stopwatch.Stop();
TimeSpan ts = stopwatch.Elapsed;

Console.WriteLine(ts.ToString());

Eu gostaria que tivéssemos algumas classes de matriz acelerada de hardware de alto nível, como a equipe do .NET, que fizeram com a System.Numerics.Vectorsclasse!

C # poderia ser a melhor linguagem ML com um pouco mais de trabalho nessa área!

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.