Продуктивність масивів проти списків


194

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

Взагалі можна було б скористатися списками (списками) через їх гнучкість у розмірі. На додаток до цього, документація про 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
Цікава деталь: Ось часи ВІДПОВІДЖЕННЯ / ВІДКЛЮЧЕННЯ на моїй установці (.net 3.5 sp1): 0,92, 0,80, 0,96, 0,93; що підказує мені, що в VM є деякий інтелект для оптимізації масиву / циклу приблизно на 10% краще, ніж в інших випадках.
Девід Шмітт

2
Так, існує оптимізація JIT для масиву / для. Насправді, у мене склалося враження, що він включає в себе справу «Довжина» (оскільки вона знає, що це виправлено), отже, чому я її не витягнув першим (на відміну від «Список», де я це робив). Дякуємо за оновлення.
Марк Гравелл

2
Дивно. Мої дуже схожі тести не показують суттєвої різниці між функцією for та foreach при використанні масивів. Буду ретельно досліджувати в публікації в блозі з орієнтиром, за яким можуть працювати інші люди, і надсилає мені результати для ...
Джон Скіт

1
Я отримую різно різні результати, якщо використовувати різну змінну для chk для кожного тесту (chk1, chk2, chk3, chk4). Список / для = 1303 мс, масив / для = 433 мс. Будь-які ідеї чому?
Джон

4
Посилання, згадане у коментарі Джона до блогу Джона Скіта, було порушено. Нижче оновлене посилання. codeblog.jonskeet.uk/2009/01/29/…
Josh

88

Підсумок:

  • Масив потрібно використовувати:

    • Так часто можливо. Це швидко і займає найменший діапазон оперативної пам’яті для тієї ж кількості інформації.
    • Якщо ви знаєте точний підрахунок комірок
    • Якщо дані збережені в масиві <85000 b (85000/32 = 2656 елементів для цілих даних)
    • При необхідності висока швидкість випадкового доступу
  • Список потрібно використовувати:

    • Якщо потрібно, щоб додати комірки до кінця списку (часто)
    • Якщо потрібно, щоб додати комірки на початку / середині списку (НЕ ОФТЕН)
    • Якщо дані збережені в масиві <85000 b (85000/32 = 2656 елементів для цілих даних)
    • При необхідності висока швидкість випадкового доступу
  • LinkedList потрібно використовувати:

    • Якщо потрібно, щоб додати комірки на початку / середині / в кінці списку (часто)

    • За потреби лише послідовний доступ (вперед / назад)

    • Якщо вам потрібно зберегти ВЕЛИКІ предмети, але кількість елементів невелика.

    • Краще не використовувати для великої кількості елементів, так як використовувати додаткову пам'ять для посилань.

      Якщо ви не впевнені, що вам потрібен LinkedList - НЕ ТРЕБУЄТЕ його.


Детальніше:

кольорове значення

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

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

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


Я трохи збентежений вашим твердженням, що подання списку відносно швидко, але введення відбувається повільно. Вставлення також лінійний час і швидше в середньому на 50%, ніж препенд.
Майк Мариновський

1
@MikeMarynowski у списку c # обгортка навколо масиву. Тож у випадку вставки до списку у вас буде лінійний час лише до певного моменту. Після цього система створить новий більший масив і знадобиться час для копіювання елементів зі старого.
Андрій

Те ж саме і з препендами.
Майк Мариновський

Операція подачі - це лише вставка на 0. Це найгірший випадок з точки зору продуктивності, тому якщо вставка повільна, подача ще повільніше.
Майк Мариновський

Як вставка, так і додавання є O (n) (амортизовано). Приставка - це вставка, але це найповільніша можлива вставка, оскільки вона повинна переміщувати ВСІ елементи зі списку на одну точку вгору. Вставка у випадковому розташуванні повинна рухати лише елементи, які знаходяться на більш високому індексі, ніж точка вставки, тому в середньому 50% елементів.
Майк Мариновський

26

Думаю, вистава буде досить схожа. Накладні витрати, які використовуються при використанні списку проти масиву, - це 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];
}

Як ви бачите, коли ви використовуєте індексатор списку, Список здійснює перевірку, чи не виходите ви за межі внутрішнього масиву. Ця додаткова перевірка поставляється з вартістю.


Привіт Фредерік, спасибі! Як би ви пояснили, що ваш список зайняв удвічі більший час масивів. Не те, що ви очікували. Ви намагалися збільшити кількість елементів?
Боаз

1
Не поверне це._items [індекс]; вже кинути виняток, якщо індекс виявився поза діапазоном? Чому .NET має додаткову перевірку, коли кінцевий результат збігається з ним або без нього?
Джон Мерсьє

@John Mercier перевірка відповідає розміру списку (кількість елементів, що містяться в даний час), який відрізняється і, ймовірно, менший, ніж ємність масиву _items. Масив виділяється із зайвою ємністю, щоб швидше додавати майбутні елементи, не вимагаючи перерозподілу для кожного додавання.
Трасві

21

якщо ви просто отримуєте одне значення з будь-якого (не в циклі), то обидва перевіряють межі (ви в керованому коді пам'ятаєте), це лише список робить це двічі. Дивіться замітки пізніше, чому це, ймовірно, не є великою справою.

Якщо ви використовуєте свій власний для (int int i = 0; i <x. [Довжина / кількість]; i ++), ключова різниця полягає в наступному:

  • Масив:
    • перевірка меж знімається
  • Списки
    • проводиться перевірка меж

Якщо ви використовуєте foreach, ключова різниця полягає в наступному:

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

Перевірка меж часто не є великою справою (особливо якщо ви працюєте на процесорі з глибоким прогнозом і галузевим прогнозуванням - норма для більшості цих днів), але тільки ваше власне профілювання може сказати вам, якщо це проблема. Якщо ви знаходитесь в частинах свого коду, де ви уникаєте розподілу купи (хорошими прикладами є бібліотеки чи реалізація хеш-коду), то переконайтеся, що змінна вводиться, оскільки список не IList дозволить уникнути цього невдачі. Як завжди, профіль, якщо це має значення.


11

[ Див. Також це питання ]

Я змінив відповідь Марка, щоб використовувати фактичні випадкові числа і фактично виконувати ту саму роботу у всіх випадках.

Результати:

         для передбачення
Масив: 1575 мс 1575 мс (+ 0%)
Список: 1630ms 2627ms (+ 61%)
         (+ 3%) (+ 67%)

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

Складено як випуск під VS 2008 SP1. Запуск без налагодження на Q6600@2.40GHz, .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

Ось такий, який використовує словники, IEumerable:

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

Не намагайтеся додати потужність, збільшуючи кількість елементів.

Продуктивність

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

Я змінюю розмір масиву в 60 тис. Разів буде повільним ... Хоча, звичайно, у реальному використанні підхід просто перевірить, скільки додаткових слотів вам потрібно, змінити розмір його на довжину + 60 к і потім простежити через вставки.
tobriand

Змінення розміру масиву відбувається дуже швидко, якщо ви подвоюєте розмір кожного разу, коли вам знадобиться більше місця. Я виявив, що, здається, це займає приблизно той самий час, як це робиться, оскільки він просто змінює його розмір один раз після первинної декларації. Це дає вам гнучкість списку та більшу частину швидкості масиву.
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;
}

0

Оскільки List <> використовує масиви внутрішньо, основна продуктивність повинна бути однаковою. Дві причини, чому Список може бути трохи повільнішим:

  • Щоб шукати елемент у списку, викликається метод List, який виконує пошук у нижньому масиві. Тож вам потрібен додатковий виклик методу. З іншого боку, компілятор може розпізнати це та оптимізувати "зайвий" виклик.
  • Компілятор може зробити деякі спеціальні оптимізації, якщо він знає розмір масиву, який він не може зробити для списку невідомої довжини. Це може принести певне поліпшення продуктивності, якщо у вашому списку є лише кілька елементів.

Щоб перевірити, чи має це значення для вас, можливо, найкраще відрегулювати розміщені функції синхронізації під перелік розміру, який ви плануєте використовувати, і побачити, як виглядають результати для вашого особливого випадку.


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

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 зробили з System.Numerics.Vectorsкласом!

C # міг би бути найкращим мовою ML з трохи більше роботи в цій галузі!

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.