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

    static void Main(string[] args)
    {
        int k = 1000 * 1000;

        List<int> l = new List<int>();
        for (int i = 0; i < k; i++)
        {
            l.Add(i);
        }

        for (int i = 0; i < 10; i++)
        {
            var res = With_timer(() => l.IndexOf(0));
        }

        Console.ReadKey(true);
}

In this case, the first execute slower

< Сильный > UPD

private static T With_timer<T>(Func<T> action)
    {
        Stopwatch sw = Stopwatch.StartNew();
        var result = action();
        sw.Stop();

        Console.WriteLine($"TotalMilliseconds: {sw.Elapsed.TotalMilliseconds}");

        return result;
    }

< Сильный > UPD2 Вынести действие в отдельный метод

 private static void TestMethod(List<int> l)
    {
        for (int i = 0; i < 15; i++)
        {
            var res = With_timer(() => l.IndexOf(i));
        }
    }

И вызовите его дважды в основном методе с разными индексами: введите описание изображения здесь

1
Sergey Gornostaev 25 Сен 2018 в 20:33

2 ответа

Лучший ответ

Скорость обусловлена тем, что метод IndexOf и StopWatch являются jhibited при первом обращении к нему. Вы можете изменить свой код, чтобы вызывать эти методы вне цикла хотя бы один раз, как только вы сделаете это, все записанные времена доступа будут согласованы.

Полный MCVE

static void Main(string[] args)
{
    var unrelatedList = new List<int>(1) { 1 };
    var jitMe = unrelatedList.IndexOf(1);
    var sw = Stopwatch.StartNew();
    sw.Stop();
    Console.WriteLine($"Initialized {jitMe}, {sw.Elapsed.TotalMilliseconds}");
    Console.WriteLine();


    const int k = 1000 * 1000;
    var l = new List<int>(k);
    for (var i = 0; i < k; i++)
    {
        l.Add(i);
    }

    for (var i = 0; i < 10; i++)
    {
        sw = Stopwatch.StartNew();
        var itm = l.IndexOf(0);
        sw.Stop();
        Console.WriteLine($"TotalMilliseconds: {sw.Elapsed.TotalMilliseconds}, {itm}");
    }

    Console.WriteLine("Done");
    Console.ReadLine();
}

Выход

Initialized 0, 0.0015

TotalMilliseconds: 0.0005, 0
TotalMilliseconds: 0.0005, 0
TotalMilliseconds: 0.0005, 0
TotalMilliseconds: 0, 0
TotalMilliseconds: 0, 0
TotalMilliseconds: 0.0005, 0
TotalMilliseconds: 0.0005, 0
TotalMilliseconds: 0.0005, 0
TotalMilliseconds: 0.0005, 0
TotalMilliseconds: 0.0005, 0

См. Также JIT-компиляцию C # и .NET и Компиляция MSIL в собственный код

1
Igor 25 Сен 2018 в 18:15

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

Вы также можете увидеть артефакт процесса JIT. Если вы переместите это в отдельный метод и вызовете метод более одного раза, увидите ли вы аналогичные результаты в обоих вызовах?

Наконец, может быть интереснее каждый раз видеть результаты таймера для операторов .Add() или, по крайней мере, результаты таймера для доступа к каждому индексу, а не позицию 0.

0
Joel Coehoorn 25 Сен 2018 в 17:48