Производительность массивов и списков

194

Скажем, у вас должен быть список / массив целых чисел, которые вы должны часто повторять, и я имею в виду очень часто. Причины могут быть разными, но говорят, что это сердце самого внутреннего цикла обработки большого объема.

В целом, можно использовать списки (List) из-за их гибкости в размере. Кроме того, документация msdn утверждает, что списки используют массив внутри и должны работать так же быстро (быстрый просмотр с помощью Reflector подтверждает это). Тем не менее, есть некоторые накладные расходы.

Кто-нибудь на самом деле измерял это? будет ли повторение 6M раз по списку занимать то же время, что и массив?

Боаз
источник
3
Помимо проблем с производительностью, я предпочитаю использовать массивы вместо списков для их фиксированного размера (конечно, в случаях, когда изменение количества элементов не требуется). Читая существующий код, я считаю полезным быстро узнать, что элемент вынужден иметь фиксированный размер, а не проверять код ниже в функции.
Уоррен Стивенс
2
T[]против List<T>может иметь большое значение производительности. Я только что оптимизировал чрезвычайно интенсивное (вложенное) циклическое приложение для перехода от списков к массивам в .NET 4.0. Я ожидал улучшения на 5-10%, но ускорился более чем на 40%! Никаких других изменений, кроме перехода непосредственно из списка в массив. Все перечисления были сделаны с foreachзаявлениями. На основании ответа Marc Gravell, это выглядит как foreachс List<T>особенно плохо.
Специальный соус

Ответы:

221

Очень легко измерить ...

В небольшом количестве кода обработки с жестким циклом, где я знаю, что длина фиксирована, я использую массивы для этой крошечной микроптимизации; Массивы могут быть немного быстрее, если вы используете индексатор / для формы - но IIRC полагает, что это зависит от типа данных в массиве. Но если вам не нужна микрооптимизация, сделайте ее простой, используйте List<T>и т. Д.

Конечно, это применимо, только если вы читаете все данные; словарь будет быстрее для поиска на основе ключей.

Вот мои результаты с использованием «int» (второе число - контрольная сумма, чтобы убедиться, что они все сделали одну и ту же работу):

(отредактировано, чтобы исправить ошибку)

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

на основе испытательного стенда:

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
Интересная деталь: вот времена RELEASE / DEBUG на моей буровой установке (.net 3.5 sp1): 0,92, 0,80, 0,96, 0,93; что говорит мне, что в ВМ есть некоторый интеллект для оптимизации цикла Array / for примерно на 10% лучше, чем в других случаях.
Дэвид Шмитт
2
Да, есть оптимизация JIT для массива / для. На самом деле, у меня сложилось впечатление, что он включает в себя случай длины (поскольку он знает, что он исправлен), поэтому я не вытащил его первым (в отличие от Списка, где я это сделал). Спасибо за обновление.
Марк Гравелл
2
Weird. Мои очень похожие тесты не показывают значительной разницы между for и foreach при использовании массивов. Тщательно рассмотрит в сообщении в блоге с тестом, который могут запустить другие люди, и отправит мне результаты за ...
Джон Скит
1
Я получаю совершенно разные результаты, если использую разные переменные для chk для каждого теста (chk1, chk2, chk3, chk4). Список / для = 1303мс, массив / для = 433мс. Есть идеи почему?
Джон
4
Ссылка, упомянутая в приведенном выше комментарии Джона на блог Джона Скита, была разорвана. Ниже обновленная ссылка. codeblog.jonskeet.uk/2009/01/29/…
Джош Делонг
88

Резюме:

  • Массив нужно использовать:

    • Так часто, как это возможно. Это быстро и занимает наименьший диапазон оперативной памяти для того же объема информации.
    • Если вы знаете точное количество клеток, необходимых
    • Если данные сохранены в массиве <85000 b (85000/32 = 2656 элементов для целочисленных данных)
    • При необходимости высокая скорость произвольного доступа
  • Список нужно использовать:

    • При необходимости добавить ячейки в конец списка (часто)
    • При необходимости добавить ячейки в начале / середине списка (НЕ ЧАСТО)
    • Если данные сохранены в массиве <85000 b (85000/32 = 2656 элементов для целочисленных данных)
    • При необходимости высокая скорость произвольного доступа
  • LinkedList нужно использовать:

    • При необходимости добавить ячейки в начале / середине / конце списка (часто)

    • При необходимости только последовательный доступ (вперед / назад)

    • Если вам нужно сохранить БОЛЬШИЕ предметы, но количество предметов мало.

    • Лучше не использовать для большого количества элементов, так как он использует дополнительную память для ссылок.

      Если вы не уверены, что вам нужен LinkedList - ВАМ НЕ НУЖНО.


Больше деталей:

цветовой смысл

Массив против Списка против Связанного Списка

Намного больше деталей:

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

Андрей
источник
Меня немного смущает ваше утверждение, что добавление в список происходит относительно быстро, но вставка идет медленно. Вставка также является линейным временем и быстрее на 50% в среднем, чем предварительная.
Майк Мариновски,
1
@MikeMarynowski в c # list является оберткой вокруг Array. Так что в случае вставки в список вы будете иметь линейное время только до некоторой точки. После этого система создаст новый массив большего размера и потребуется время для копирования элементов из старого.
Эндрю
То же самое с prepends.
Майк Мариновски,
Операция prepend - это просто вставка в 0. Это худший случай с точки зрения производительности, поэтому, если вставка медленная, prepend еще медленнее.
Майк Мариновски,
И вставка, и prepend - это O (n) (амортизировано). Вставка - это вставка, но это самая медленная из возможных вставок, потому что она должна переместить ВСЕ элементы в списке на одну позицию вверх. Вставка в случайном месте должна только перемещать элементы с более высоким индексом, чем точка вставки, т.е. в среднем 50% элементов.
Майк Мариновски,
26

Я думаю, что производительность будет довольно похожа. Служебная нагрузка, которая возникает при использовании List vs Array, - IMHO, когда вы добавляете элементы в список, и когда список должен увеличивать размер используемого им массива, когда достигается емкость массива.

Предположим, у вас есть список с вместимостью 10, тогда список увеличит его емкость, как только вы захотите добавить 11-й элемент. Вы можете уменьшить влияние на производительность, инициализируя Емкость списка для количества элементов, которые он будет содержать.

Но чтобы выяснить, выполняется ли итерация по списку так же быстро, как итерация по массиву, почему бы вам не провести сравнительный анализ?

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

В моей системе; перебор массива занял 33 мсек; перебор списка занял 66 мсек.

Честно говоря, я не ожидал, что вариация будет такой большой. Итак, я поместил свою итерацию в цикл: теперь я выполняю обе итерации 1000 раз. Результаты:

повторение списка заняло 67146 мсек повторение массива заняло 40821 мсек

Теперь, вариация уже не так велика, но все же ...

Поэтому я запустил .NET Reflector, и получатель индексатора класса List выглядит следующим образом:

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

Как вы можете видеть, когда вы используете индексатор List, List выполняет проверку, не выходите ли вы за пределы внутреннего массива. Эта дополнительная проверка поставляется с оплатой.

Фредерик Гейсель
источник
Привет Фредерик, спасибо! Как бы вы объяснили, что ваш список занял вдвое больше времени, чем массивы. Не то, что вы ожидаете. Вы пытались увеличить количество элементов?
Вооз
1
Не вернет this._items [index]; уже бросить исключение, если индекс был вне диапазона? Почему .NET имеет эту дополнительную проверку, когда конечный результат одинаков с или без него?
Джон Мерсье
@John Mercier - проверка на размер списка (количество элементов, содержащихся в данный момент), который отличается от объема массива _items и, вероятно, меньше его. Массив выделяется с избыточной емкостью, чтобы ускорить добавление будущих элементов, не требуя перераспределения для каждого добавления.
Трасви
21

если вы просто получаете одно значение из любого (не в цикле), тогда оба выполняют проверку границ (вы помните, что вы в управляемом коде), просто список делает это дважды. См. Примечания позже, почему это, вероятно, не имеет большого значения.

Если вы используете свой собственный для (int int i = 0; i <x. [Length / Count]; i ++), то ключевое отличие будет следующим:

  • Массив:
    • проверка границ снята
  • Списки
    • проверка границ выполняется

Если вы используете foreach, то ключевое отличие заключается в следующем:

  • Массив:
    • для управления итерацией не выделено ни одного объекта
    • проверка границ снята
  • Список с помощью переменной, известной как List.
    • переменная управления итерациями размещена в стеке
    • проверка границ выполняется
  • Список через переменную, известную как IList.
    • переменная управления итерацией выделена кучей
    • проверка границ выполняется также, значения списков не могут быть изменены во время foreach, тогда как значения массива могут быть.

Проверка границ часто не имеет большого значения (особенно если вы работаете на процессоре с глубоким прогнозированием конвейера и ветвлений - нормой для большинства в наши дни), но только ваше собственное профилирование может сказать вам, если это проблема. Если вы находитесь в частях своего кода, где избегаете выделения кучи (хорошими примерами являются библиотеки или реализации хэш-кода), то гарантируя, что переменная типизирована как List not IList, позволит избежать этой ловушки. Как всегда профиль, если это имеет значение.

ShuggyCoUk
источник
11

[ Смотрите также этот вопрос ]

Я изменил ответ Марка, чтобы использовать реальные случайные числа и фактически делать ту же работу во всех случаях.

Полученные результаты:

         для каждого
Массив: 1575мс 1575мс (+ 0%)
Список: 1630 мс 2627 мс (+ 61%)
         (+ 3%) (+ 67%)

(Контрольная сумма: -1000038876)

Составлено как релиз под VS 2008 SP1. Работает без отладки на Q6600 @ 2,40 ГГц, .NET 3.5 SP1.

Код:

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();
    }
}
Дэвид Шмитт
источник
Это странно - я просто запустил ваш точный код, собранный из командной строки (3.5SP1) с помощью / o + / debug-, и мои результаты: list / for: 1524; массив / для: 1472; Список / Еогеасп: 4128; Массив / Еогеасп: 1484.
Джон Скит
Вы говорите, что это было скомпилировано как релиз - могу я просто проверить, что вы его запустили, а не отлаживали? Глупый вопрос, я знаю, но иначе не могу объяснить результаты ...
Джон Скит
2

Измерения хороши, но вы получите значительно разные результаты в зависимости от того, что именно вы делаете в своем внутреннем цикле. Измерьте вашу собственную ситуацию. Если вы используете многопоточность, это само по себе нетривиальное занятие.

Стефан Эггермонт
источник
2

Действительно, если вы выполняете некоторые сложные вычисления внутри цикла, то производительность индексатора массива по сравнению с индексатором списка может быть настолько незначительно, что в конечном итоге это не имеет значения.

Фредерик Гейсель
источник
2

Вот тот, который использует словари, 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
источник
2

Не пытайтесь увеличить емкость, увеличив количество элементов.

Производительность

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
Фатих ГЮРДАЛЬ
источник
Я получаю, что изменение размера массива 60k раз будет медленным ... Конечно, при реальном использовании, подход будет просто проверить, сколько дополнительных слотов вам нужно, изменить его размер до длины + 60 Кб, а затем сжать вставки.
августа
Изменение размера массива очень быстро, если вы удваиваете размер каждый раз, когда вам нужно больше места. Я обнаружил, что, кажется, на это уходит примерно столько же времени, сколько на изменение размера после первоначального объявления. Это дает вам гибкость списка и большую часть скорости массива.
user1318499
2

Я беспокоился о том, что тесты, опубликованные в других ответах, все равно оставят возможность компилятору оптимизировать, устранить или объединить циклы, поэтому я написал такой, который:

  • Используются непредсказуемые входы (случайные)
  • Запускает вычисленный результат с выводом на консоль
  • Изменяет входные данные при каждом повторении

В результате производительность прямого массива примерно на 250% выше, чем доступа к массиву, заключенному в IList:

  • 1 миллиард обращений к массиву: 4000 мс
  • 1 миллиард обращений к списку: 10000 мс
  • 100 миллионов обращений к массиву: 350 мс
  • 100 миллионов обращений к списку: 1000 мс

Вот код:

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
источник
0

Поскольку List <> использует массивы внутри, базовая производительность должна быть одинаковой. Две причины, почему список может быть немного медленнее:

  • Для поиска элемента в списке вызывается метод List, который выполняет поиск в базовом массиве. Так что вам нужен дополнительный вызов метода там. С другой стороны, компилятор может распознать это и оптимизировать «ненужный» вызов.
  • Компилятор может выполнить некоторые специальные оптимизации, если он знает размер массива, что он не может сделать для списка неизвестной длины. Это может принести некоторое улучшение производительности, если в вашем списке всего несколько элементов.

Чтобы проверить, имеет ли это какое-то значение для вас, вероятно, лучше отрегулировать опубликованные функции синхронизации в соответствии со списком размера, который вы планируете использовать, и посмотреть, каковы результаты для вашего особого случая.

STH
источник
0

Поскольку у меня был похожий вопрос, это заставило меня быстро начать.

Мой вопрос немного более конкретен: «Какой самый быстрый метод для реализации рефлексивного массива»

Тестирование, проведенное Марком Гравеллом, показывает многое, но не совсем точно по времени доступа. Его время включает в себя зацикливание массива и списков, а также. Так как я также придумал третий метод, который я хотел протестировать, «Словарь», просто для сравнения я расширил код теста для истории.

Во-первых, я делаю тест с использованием константы, которая дает мне определенное время, включая цикл. Это «голое» время, исключающее фактический доступ. Затем я делаю тест с доступом к предметной структуре, это дает мне время, циклы и фактический доступ.

Разница между «голым» временем и временем, включенным в накладные расходы, дает мне представление о времени доступа к структуре.

Но насколько точно это время? Во время теста окна сделают некоторое время нарезки для shure. У меня нет информации о срезах времени, но я полагаю, что они равномерно распределены во время теста и порядка десятков мс, что означает, что точность синхронизации должна быть порядка +/- 100 мс или около того. Немного грубая оценка? В любом случае источник систематической ошибки.

Кроме того, тесты проводились в режиме «Отладка» без оптимизации. В противном случае компилятор может изменить реальный тестовый код.

Итак, я получаю два результата: один для константы, помеченный «(c)», а другой для доступа, помеченный «(n)», а разница «dt» говорит мне, сколько времени занимает фактический доступ.

И вот результаты:

          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

С лучшими оценками ошибок синхронизации (как устранить систематическую ошибку измерения из-за временного среза?) Можно было бы сказать больше о результатах.

Похоже, что List / foreach имеет самый быстрый доступ, но накладные расходы убивают его.

Разница между List / for и List / foreach является странной. Может быть, это связано с обналичиванием денег?

Кроме того, для доступа к массиву не имеет значения, используете ли вы forцикл или foreachцикл. Результаты расчета времени и его точность делают результаты «сопоставимыми».

Использование словаря является самым медленным, я рассмотрел его только потому, что с левой стороны (индексатор) у меня есть разреженный список целых чисел, а не диапазон, который используется в этих тестах.

Вот модифицированный тестовый код.

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
источник
0

В некоторых кратких тестах я обнаружил, что комбинация двух из них лучше в том, что я бы назвал достаточно интенсивной математикой:

Тип: List<double[]>

Время: 00: 00: 05.1861300

Тип: List<List<double>>

Время: 00: 00: 05.7941351

Тип: double[rows * columns]

Время: 00: 00: 06.0547118

Выполнение кода:

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

Мне бы очень хотелось, чтобы у нас были классные матричные классы с аппаратным ускорением, такие как .NET Team с System.Numerics.Vectorsклассом!

C # может быть лучшим языком ML с немного большим количеством работы в этой области!

ржавый гвоздь
источник