Rendimiento de matrices frente a listas

192

Digamos que necesita tener una lista / matriz de enteros que necesita iterar con frecuencia, y me refiero con mucha frecuencia. Las razones pueden variar, pero digamos que está en el corazón del bucle más interno de un procesamiento de alto volumen.

En general, uno optaría por usar Listas (Lista) debido a su flexibilidad de tamaño. Además de eso, la documentación de msdn afirma que las Listas usan una matriz internamente y deben funcionar igual de rápido (un vistazo rápido con Reflector lo confirma). Nunca, hay algunos gastos generales involucrados.

¿Alguien realmente midió esto? ¿iterar 6M veces a través de una lista tomaría el mismo tiempo que una matriz?

Booz
fuente
3
Dejando a un lado los problemas de rendimiento, prefiero el uso de matrices en lugar de listas para su tamaño fijo (en casos donde, por supuesto, no es necesario cambiar el número de elementos). Al leer el código existente, me resulta útil saber rápidamente que un elemento se ve obligado a tener un tamaño fijo, en lugar de tener que inspeccionar el código más abajo en la función.
Warren Stevens
2
T[]vs. List<T>puede hacer una gran diferencia de rendimiento. Acabo de optimizar una aplicación extremadamente intensiva (anidada) para pasar de listas a matrices en .NET 4.0. Esperaba una mejora del 5% al ​​10%, ¡pero obtuve más del 40% de aceleración! No hay otros cambios que pasar directamente de la lista a la matriz. Todas las enumeraciones se hicieron con foreachdeclaraciones. Sobre la base de la respuesta de Marc Gravell, parece que foreachcon List<T>es particularmente malo.
Salsa especial

Respuestas:

221

Muy fácil de medir ...

En una pequeña cantidad de código de procesamiento de lazo cerrado donde sé que la longitud es fija , uso matrices para ese pequeño bit adicional de micro-optimización; las matrices pueden ser marginalmente más rápidas si usa el indexador / para el formulario, pero el IIRC cree que depende del tipo de datos en la matriz. Pero a menos que necesite micro-optimizar, manténgalo simple y useList<T> etc.

Por supuesto, esto solo se aplica si está leyendo todos los datos; un diccionario sería más rápido para búsquedas basadas en claves.

Aquí están mis resultados usando "int" (el segundo número es una suma de verificación para verificar que todos hicieron el mismo trabajo):

(editado para corregir errores)

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

basado en la plataforma de prueba:

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();
    }
}
Marc Gravell
fuente
8
Detalles interesantes: Estos son los tiempos de LIBERACIÓN / DEPURACIÓN en mi equipo (.net 3.5 sp1): 0.92, 0.80, 0.96, 0.93; lo que me dice que hay algo de inteligencia en la VM para optimizar el Array / for loop aproximadamente un 10% mejor que los otros casos.
David Schmitt
2
Sí, hay optimización JIT para array / for. En realidad, tenía la impresión de que incluía el caso de Longitud (ya que sabe que está solucionado), por lo tanto, por qué no lo saqué primero (a diferencia de la Lista donde lo hice). Gracias por la actualización.
Marc Gravell
2
Extraño. Mis pruebas muy similares no muestran diferencias significativas entre for y foreach cuando uso matrices. Investigaré a fondo en una publicación de blog con un punto de referencia que otras personas pueden ejecutar y me enviarán resultados para ...
Jon Skeet
1
Obtengo resultados dramáticamente diferentes si uso una variable diferente para chk para cada prueba (chk1, chk2, chk3, chk4). Lista / for = 1303ms, Array / for = 433ms. Alguna idea de por qué?
Jon
44
El enlace mencionado en el comentario anterior de Jon al blog de Jon Skeet estaba roto. A continuación se muestra el enlace actualizado. codeblog.jonskeet.uk/2009/01/29/…
Josh DeLong
88

Resumen:

  • Matriz necesita usar:

    • Tan a menudo como sea posible. Es rápido y toma el rango de RAM más pequeño para la misma cantidad de información.
    • Si sabe el recuento exacto de células necesarias
    • Si los datos se guardaron en una matriz <85000 b (85000/32 = 2656 elementos para datos enteros)
    • Si es necesario, alta velocidad de acceso aleatorio
  • Lista necesita usar:

    • Si es necesario agregar celdas al final de la lista (a menudo)
    • Si es necesario agregar celdas al comienzo / medio de la lista (NO A MENUDO)
    • Si los datos se guardaron en una matriz <85000 b (85000/32 = 2656 elementos para datos enteros)
    • Si es necesario, alta velocidad de acceso aleatorio
  • LinkedList necesita usar:

    • Si es necesario agregar celdas al principio / medio / final de la lista (a menudo)
    • Si es necesario solo acceso secuencial (adelante / atrás)
    • Si necesita guardar artículos GRANDES, pero el recuento de artículos es bajo.
    • Mejor no lo use para una gran cantidad de elementos, ya que usa memoria adicional para los enlaces.

Más detalles:

significado del color

Matriz vs Lista vs Lista Vinculada

Mucho más detalles:

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

Andrés
fuente
Estoy un poco confundido por su afirmación de que la lista anterior es relativamente rápida pero la inserción es lenta. La inserción también es tiempo lineal, y más rápido en un 50% en promedio que lo anterior.
Mike Marynowski el
1
@MikeMarynowski en la lista de C # es una envoltura alrededor de Array. Entonces, en caso de inserción en la lista, tendrá tiempo lineal solo hasta cierto punto. Después de este sistema creará una nueva matriz más grande y necesitará tiempo para copiar elementos de la anterior.
Andrew
Lo mismo con prepends.
Mike Marynowski
Una operación de anteponer es solo una inserción en 0. Es el peor caso de inserción en términos de rendimiento, por lo que si la inserción es lenta, el anteponer es aún más lento.
Mike Marynowski
Tanto insertar como anteponer es O (n) (amortizado). Un antecedente ES un inserto, pero es el inserto más lento posible porque tiene que mover TODOS los elementos de la lista un punto hacia arriba. Una inserción en una ubicación aleatoria solo tiene que subir los elementos que están en un índice más alto que el punto de inserción, por lo que el 50% de los elementos en promedio.
Mike Marynowski
26

Creo que el rendimiento será bastante similar. La sobrecarga que está involucrada cuando se usa una lista frente a una matriz es, en mi humilde opinión, cuando agrega elementos a la lista, y cuando la lista tiene que aumentar el tamaño de la matriz que está utilizando internamente, cuando se alcanza la capacidad de la matriz.

Suponga que tiene una Lista con una capacidad de 10, luego la Lista aumentará su capacidad una vez que desee agregar el undécimo elemento. Puede disminuir el impacto en el rendimiento inicializando la Capacidad de la lista en la cantidad de elementos que contendrá.

Pero, para saber si iterar sobre una Lista es tan rápido como iterar sobre una matriz, ¿por qué no lo compara?

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();

En mi sistema; iterar sobre la matriz tomó 33 ms; iterar sobre la lista tomó 66 ms.

Para ser sincero, no esperaba que la variación fuera tanto. Entonces, puse mi iteración en un bucle: ahora, ejecuto ambas iteraciones 1000 veces. Los resultados son:

iterar la lista tomó 67146 mseg iterar la matriz tomó 40821 mseg

Ahora, la variación ya no es tan grande, pero aún así ...

Por lo tanto, he iniciado .NET Reflector, y el captador del indexador de la clase Lista se ve así:

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

Como puede ver, cuando usa el indexador de la Lista, la Lista realiza una verificación si no está saliendo de los límites de la matriz interna. Este cheque adicional tiene un costo.

Frederik Gheysels
fuente
Hola Frederik, gracias! ¿Cómo explicaría que su lista tomó el doble de tiempo que las matrices? No es lo que esperarías. ¿Intentaste aumentar el número de elementos?
Boaz
1
No devolvería esto._items [index]; ¿Ya lanzó una excepción si el índice estaba fuera de rango? ¿Por qué .NET tiene esta comprobación adicional cuando el resultado final es el mismo con o sin él?
John Mercier
@John Mercier, el cheque se compara con el Tamaño de la lista (número de elementos actualmente contenidos), que es diferente y probablemente menor que la capacidad de la matriz _items. La matriz se asigna con un exceso de capacidad para acelerar la adición de elementos futuros al no requerir una reasignación para cada adición.
Trasvi
21

si solo está obteniendo un valor único de cualquiera de ellos (no en un bucle), ambos hacen la verificación de límites (está en código administrado, recuerde) es solo que la lista lo hace dos veces. Vea las notas más adelante para saber por qué esto probablemente no sea un gran problema.

Si está utilizando el suyo propio (int int i = 0; i <x. [Length / Count]; i ++), la diferencia clave es la siguiente:

  • Formación:
    • se elimina la comprobación de límites
  • Liza
    • se realiza la comprobación de límites

Si está utilizando foreach, la diferencia clave es la siguiente:

  • Formación:
    • no se asigna ningún objeto para administrar la iteración
    • se elimina la comprobación de límites
  • Lista a través de una variable conocida como Lista.
    • la variable de gestión de iteración se asigna en pila
    • se realiza la comprobación de límites
  • Lista a través de una variable que se sabe que es IList.
    • la variable de gestión de iteración está asignada en el montón
    • la verificación de límites se realiza también. Los valores de las listas no pueden modificarse durante el foreach, mientras que los valores de la matriz sí pueden modificarse.

La verificación de límites a menudo no es gran cosa (especialmente si está en una CPU con una tubería profunda y predicción de ramificación, la norma para la mayoría de estos días), pero solo su propio perfil puede decirle si eso es un problema. Si está en partes de su código donde está evitando las asignaciones de montón (buenos ejemplos son bibliotecas o en implementaciones de hashcode), entonces asegurarse de que la variable se escriba como List not IList evitará esa trampa. Como siempre perfil si importa.

ShuggyCoUk
fuente
11

[ Ver también esta pregunta ]

Modifiqué la respuesta de Marc para usar números aleatorios reales y en realidad hago el mismo trabajo en todos los casos.

Resultados:

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

(Suma de verificación: -1000038876)

Compilado como lanzamiento bajo VS 2008 SP1. Ejecutando sin depurar en un [email protected], .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();
    }
}
David Schmitt
fuente
Eso es extraño: acabo de ejecutar su código exacto, creado desde la línea de comandos (3.5SP1) con / o + / debug- y mis resultados son: list / for: 1524; matriz / para: 1472; lista / foreach: 4128; matriz / foreach: 1484.
Jon Skeet el
Dice que esto se compiló como lanzamiento. ¿Puedo verificar que lo ejecutó en lugar de depurarlo? Pregunta tonta, lo sé, pero no puedo explicar los resultados de otra manera ...
Jon Skeet
2

Las mediciones son buenas, pero obtendrás resultados significativamente diferentes dependiendo de lo que estés haciendo exactamente en tu ciclo interno. Mide tu propia situación. Si está utilizando subprocesos múltiples, eso solo es una actividad no trivial.

Stephan Eggermont
fuente
2

De hecho, si realiza algunos cálculos complejos dentro del bucle, entonces el rendimiento del indexador de matriz frente al indexador de lista puede ser tan marginalmente pequeño que, eventualmente, no importa.

Frederik Gheysels
fuente
2

Aquí hay uno que usa Diccionarios, 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();
    }
}
Travis
fuente
2

No intente agregar capacidad aumentando el número de elementos.

Actuación

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
Fatih GÜRDAL
fuente
Obtengo que cambiar el tamaño de una matriz 60k va a ser lento ... Sin embargo, seguramente en el uso en el mundo real, el enfoque sería simplemente verificar cuántas ranuras adicionales necesitas, cambiar el tamaño a una longitud + 60k y luego pasar a través de los insertos.
tobriand
Cambiar el tamaño de una matriz es muy rápido si duplica el tamaño cada vez que encuentra que necesita más espacio. Descubrí que parece tomar casi el mismo tiempo hacerlo, ya que solo cambia el tamaño una vez después de la declaración inicial. Eso le brinda la flexibilidad de una lista y la mayor parte de la velocidad de una matriz.
usuario1318499
2

Me preocupaba que los puntos de referencia publicados en otras respuestas aún dejaran espacio para que el compilador optimizara, eliminara o combinara bucles, así que escribí uno que:

  • Entradas impredecibles usadas (aleatorias)
  • Ejecuta un cálculo con el resultado impreso en la consola.
  • Modifica los datos de entrada cada repetición

El resultado es que una matriz directa tiene un rendimiento aproximadamente 250% mejor que el acceso a una matriz envuelta en una lista IL:

  • Mil millones de accesos de matriz: 4000 ms
  • Mil millones de accesos a la lista: 10000 ms
  • 100 millones de accesos de matriz: 350 ms
  • 100 millones de accesos a la lista: 1000 ms

Aquí está el 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;
}
Cygon
fuente
0

Como List <> usa arrays internamente, el rendimiento básico debe ser el mismo. Dos razones por las cuales la Lista podría ser un poco más lenta:

  • Para buscar un elemento en la lista, se llama a un método de Lista, que realiza la búsqueda en la matriz subyacente. Entonces necesita un método adicional para llamar allí. Por otro lado, el compilador podría reconocer esto y optimizar la llamada "innecesaria".
  • El compilador podría hacer algunas optimizaciones especiales si conoce el tamaño de la matriz, que no puede hacer para una lista de longitud desconocida. Esto puede traer alguna mejora en el rendimiento si solo tiene unos pocos elementos en su lista.

Para verificar si hay alguna diferencia para usted, probablemente sea mejor ajustar las funciones de temporización publicadas a una lista del tamaño que planea usar y ver cómo son los resultados para su caso especial.

algo
fuente
0

Como tenía una pregunta similar, esto me dio un comienzo rápido.

Mi pregunta es un poco más específica, '¿cuál es el método más rápido para una implementación de matriz reflexiva'?

Las pruebas realizadas por Marc Gravell muestran mucho, pero no exactamente el tiempo de acceso. Su sincronización incluye el bucle sobre la matriz y las listas también. Como también se me ocurrió un tercer método que quería probar, un 'Diccionario', solo para comparar, extendí el código de prueba hist.

Primero, hago una prueba usando una constante, lo que me da un cierto tiempo, incluido el bucle. Este es un momento 'desnudo', excluyendo el acceso real. Luego hago una prueba con el acceso a la estructura del tema, esto me da tiempo y 'sobrecarga incluida', bucle y acceso real.

La diferencia entre el tiempo 'desnudo' y el tiempo 'sobrecargado excluido' me da una indicación del tiempo de 'acceso a la estructura'.

Pero, ¿qué tan preciso es este momento? Durante la prueba, las ventanas harán algo de tiempo para cortar shure. No tengo información sobre la división del tiempo, pero supongo que se distribuye uniformemente durante la prueba y en el orden de decenas de ms, lo que significa que la precisión del tiempo debe ser del orden de +/- 100 ms aproximadamente. ¿Una estimación un poco aproximada? De todos modos, una fuente de un error sistemático de medición.

Además, las pruebas se realizaron en modo 'Depuración' sin optimización. De lo contrario, el compilador podría cambiar el código de prueba real.

Entonces, obtengo dos resultados, uno para una constante, marcado '(c)', y otro para el acceso marcado '(n)' y la diferencia 'dt' me dice cuánto tiempo lleva el acceso real.

Y estos son los 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

Con mejores estimaciones sobre los errores de tiempo (¿cómo eliminar el error de medición sistemática debido a la división del tiempo?) Se podría decir más sobre los resultados.

Parece que List / foreach tiene el acceso más rápido, pero la sobrecarga lo está matando.

La diferencia entre List / for y List / foreach es extraña. Tal vez se trata de cobrar?

Además, para acceder a una matriz, no importa si usa un forbucle o un foreachbucle. Los resultados de tiempo y su precisión hacen que los resultados sean 'comparables'.

Usar un diccionario es, con mucho, el más lento, solo lo consideré porque en el lado izquierdo (el indexador) tengo una lista escasa de enteros y no un rango como se usa en estas pruebas.

Aquí está el código de prueba 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);
PapaAtHome
fuente
0

En algunas pruebas breves, he encontrado que una combinación de ambas es mejor en lo que llamaría Matemáticas razonablemente intensivas:

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

Ejecutando el 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());

¡Ojalá tuviéramos algunas clases de matriz acelerada por hardware de primer nivel como lo ha hecho el equipo .NET con la System.Numerics.Vectorsclase!

¡C # podría ser el mejor lenguaje ML con un poco más de trabajo en esta área!

clavo oxidado
fuente