C # List <> Add () метод производительности - PullRequest
21 голосов
/ 29 июля 2011

Я работаю с коллекцией List <>, добавляю новые объекты в коллекцию внутри 2 вложенных циклов. После завершения циклов в коллекцию добавлено около 500 000 элементов.

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

Я испробовал различные приемы (инициализация коллекции с определенным размером - 500000), заменив List <> на коллекцию LinkedList <>, но это не сильно помогло.

Можете ли вы порекомендовать мне совет для решения проблемы? Мне интересно изменить структуру на более оптимизированную - LinkedList <>, например, работает лучше, чем List <> с такими операциями, как сложение.

Метод, который обновляет список

   private void UpdateForecastList(ConcurrentDictionary<Int32, RegistroSalidaProductoPrevision> prediccion, bool soloMejoresMetodos = true)
   {
        foreach (KeyValuePair<int, RegistroSalidaProductoPrevision> kvp in prediccion)
        {
            KeyValuePair<int, RegistroSalidaProductoPrevision> localKvp = kvp;

            IList<Prediccion> pExistente = prediccionList.Where(p => p.Id == localKvp.Key).ToList();

            Articulo articulo = (articuloList.Where(a => a.Id == localKvp.Key)).First();

            if (pExistente.Count > 0)
            {
                foreach (var p in pExistente)
                {
                    prediccionList.Remove(p);
                }
            }

            if (kvp.Value.Previsiones.Count > 0)
            {
                var previsiones = kvp.Value.Previsiones.Where(prevision => prevision.Value.LPrevision[1] != null).ToList();
                int previsionesCount = previsiones.Count;

                for (int a = 0; a < previsionesCount; a++)
                {
                    var registros = previsiones[a].Value.LPrevision[1].Serie;
                    int c = registros.Count;

                    if (soloMejoresMetodos)
                    {
                        if (localKvp.Value.MejorMetodo != previsiones[a].Key) continue;
                        for (int i = 0; i < c; i++)
                        {
                            var p = new Prediccion()
                                        {
                                            Id = articulo.Id,
                                            Nombre = articulo.Codigo,
                                            Descripcion = articulo.Descripcion,
                                            NombreMetodo =
                                                Utils.SplitStringByCapitals(previsiones[a].Value.NombreMetodo),
                                            Fecha = registros[i].Fecha,
                                            PrediccionArticulo = Math.Round(registros[i].Cantidad, 2),
                                            EsMejorMetodo =
                                                (previsiones[a].Value.NombreMetodo == localKvp.Value.MejorMetodo)
                                                    ? true
                                                    : false
                                        };

                            // This line experiences performance loss
                            prediccionList.Add(p);
                        }
                    }
                    else
                    {
                        for (int i = 0; i < c; i++)
                        {
                            prediccionList.Add(new Prediccion()
                                                   {
                                                       Id = articulo.Id,
                                                       Nombre = articulo.Codigo,
                                                       Descripcion = articulo.Descripcion,
                                                       NombreMetodo = previsiones[a].Value.NombreMetodo,
                                                       Fecha = registros[i].Fecha,
                                                       PrediccionArticulo =
                                                           Math.Round(registros[i].Cantidad, 2),
                                                       EsMejorMetodo =
                                                           (previsiones[a].Value.NombreMetodo ==
                                                            localKvp.Value.MejorMetodo)
                                                               ? true
                                                               : false
                                                   });
                        }
                    }
                }
            }
            else
            {
                prediccionList.Add(new Prediccion()
                                       {
                                           Id = articulo.Id,
                                           Nombre = articulo.Codigo,
                                           Descripcion = articulo.Descripcion,
                                           NombreMetodo = kvp.Value.ErroresDatos[0].Texto,
                                       });
            }
        }
    }

Небольшое описание метода: - метод считывает объект (параллельный словарь) и обновляет список (в данном случае LinkedList) с прогнозами, соответствующими определенной статье.

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

Список инициализируется нулевыми предсказаниями, соответствующими всем статьям; так, например, если у вас есть 700 статей, в начале список будет заполнен 700 пустыми прогнозами.

Когда параллельный словарь обновляется одним из вычислительных потоков, он вызывает событие, вызывающее упомянутый выше метод, который, в свою очередь, обновляет список (gneccionList).

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

Код может показаться немного ржавым, так как я пробовал различные приемы оптимизации (заменим foreach'ы на for, вычислим число вне циклов, заменим объект List <> на LinkedList <> и т. Д.). Наконец, я пришел к выводу, что часть, которая замедляет время выполнения, - это строка "ForeccionList.Add (p);".

Объекты, которые добавляются в список, являются экземплярами класса Prediccion; этот объект я считаю не очень тяжелым, он содержит только 7 полей.

Использование памяти

Я прикрепляю результат из профилирования памяти. Используемая память не превышает 256 МБ, поэтому я не считаю, что память должна быть проблемой здесь. enter image description here

Ответы [ 8 ]

7 голосов
/ 15 января 2016

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

    foreach (KeyValuePair<int, RegistroSalidaProductoPrevision> kvp in prediccion)
    {
        KeyValuePair<int, RegistroSalidaProductoPrevision> localKvp = kvp;

        IList<Prediccion> pExistente = prediccionList.Where(p => p.Id == localKvp.Key).ToList();

        Articulo articulo = (articuloList.Where(a => a.Id == localKvp.Key)).First();

Итак, для каждого элемента в словаре (prediccion) вы выполняете итерацию по всему prediccionList.Вы реализовали алгоритм n ^ 2.Время, необходимое для выполнения этого метода, пропорционально prediccion.Count * prediccionList.Count.

Вам нужен лучший алгоритм;не быстрый сбор данных.

7 голосов
/ 29 июля 2011

По моему опыту, производительность List<T> зависит от памяти. Он всегда следует одному и тому же шаблону, вставка выполняется быстро, и производительность резко падает. На моей машине это обычно происходит, когда я нажимаю 1.2G. У меня была такая же проблема почти со всеми коллекциями, которые я пробовал, поэтому я думаю, что это скорее проблема .net, чем проблема List<T>.

Я бы порекомендовал попытаться уменьшить размер объекта, который вы используете, на 500 000 (замените long s на int s и т. Д.) И попробуйте затем.
Но будьте осторожны, даже если вам удастся заставить его работать быстро на вашем компьютере, он может превысить порог компьютера, на котором развернуто приложение.

6 голосов
/ 01 августа 2011

По мере того, как ваш список увеличивается, каждый раз, когда он расширяет , собирая мусор, framework копирует его содержимое в новое местоположение list из-за способа работы сборщика мусора. Вот почему он становится все медленнее и медленнее, когда он становится больше. ( GC на MSDN )

Возможные решения (о которых я могу подумать) - использовать список или массив с предварительно определенным размером, который вы точно не заполните, или, если это не вариант, то используйте System.Collections.Generic.LinkedList, но как вы уже пробовали это, возможно, вам придется реализовать пользовательский список, если это применимо, с одной ссылкой (LinkedList является двойной связью).

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

Также, пожалуйста, посмотрите на http://www.simple -talk.com / dotnet / performance / the-top-5-.net-memory-management-management-заблуждения / , я думаю, что это будет вам в помощь.

ОБНОВЛЕНИЕ: Индексирование должно быть дешевой операцией, но, тем не менее, вы можете попытаться прочитать предвидения [a] (и registros [i] во вложенном цикле) в локальную переменную в начале цикла, вы сохраните пара индексаций (х 100000 итераций, может иметь значение, если clr не оптимизирует это?).

6 голосов
/ 29 июля 2011

Если объекты, которые вы добавляете в список, имеют какой-либо значительный размер, возможно, вы страдаете от ограничений памяти.

Если ваш процесс 32-разрядный, вы будете ограничены до 2 ГБ, прежде чем исчерпать адресное пространство, но если он 64-разрядный, вы можете легко превысить физическую память в машине и начать подкачку диск.

Насколько велики ваши объекты?

2 голосов
/ 15 января 2016

Использование структуры вместо класса может значительно улучшить вашу производительность.

Вы также можете повысить производительность, потеряв свои свойства String из класса / структуры Prediccion.

Я давно интересовался фактическим воздействием, поэтому вот мой контрольный показатель:

Я взял разные структуры данных и поместил в них 20 миллионов объектов / структур. Вот результат:

List:
Adding 20000000 TestClass to a List`1 took 3563,2068 ms
Accessing 20000000 TestClass from a List took 103,0203 ms
Adding 20000000 TestStruct to a List`1 took 2239,9639 ms
Accessing 20000000 TestStruct from a List took 254,3245 ms

Initialized List:
Adding 20000000 TestClass to a List`1 took 3774,772 ms
Accessing 20000000 TestClass from a List took 99,0548 ms
Adding 20000000 TestStruct to a List`1 took 1520,7765 ms
Accessing 20000000 TestStruct from a List took 257,5064 ms

LinkedList:
Adding 20000000 TestClass to a LinkedList`1 took 6085,6478 ms
Adding 20000000 TestStruct to a LinkedList`1 took 7771,2243 ms

HashSet:
Adding 20000000 TestClass to a HashSet`1 took 10816,8488 ms
Adding 20000000 TestStruct to a HashSet`1 took 3694,5187 ms

Now I added a string to the class/struct:
List:
Adding 20000000 TestClassWithString to a List`1 took 4925,1215 ms
Accessing 20000000 TestClassWithString from a List took 120,0348 ms
Adding 20000000 TestStructWithString to a List`1 took 3554,7463 ms
Accessing 20000000 TestStructWithString from a List took 456,3299 ms

Это моя тестовая программа:

    static void Main(string[] args)
    {
        const int noObjects = 20*1000*1000;

        Console.WriteLine("List:");
        RunTest(new List<TestClass>(), noObjects);
        RunTest(new List<TestStruct>(), noObjects);
        Console.WriteLine();

        Console.WriteLine("Initialized List:");
        RunTest(new List<TestClass>(noObjects), noObjects);
        RunTest(new List<TestStruct>(noObjects), noObjects);
        Console.WriteLine();

        Console.WriteLine("LinkedList:");
        RunTest(new LinkedList<TestClass>(), noObjects);
        RunTest(new LinkedList<TestStruct>(), noObjects);
        Console.WriteLine();

        Console.WriteLine("HashSet:");
        RunTest(new HashSet<TestClass>(), noObjects);
        RunTest(new HashSet<TestStruct>(), noObjects);
        Console.WriteLine();

        Console.WriteLine("Now I added a string to the class/struct:");
        Console.WriteLine("List:");
        RunTest(new List<TestClassWithString>(), noObjects);
        RunTest(new List<TestStructWithString>(), noObjects);
        Console.WriteLine();

        Console.ReadLine();
    }




    private static void RunTest<T>(ICollection<T> collection, int noObjects) where T : ITestThing
    {
        Stopwatch sw = new Stopwatch();
        sw.Restart();
        for (int i = 0; i < noObjects; i++)
        {
            var obj = Activator.CreateInstance<T>();
            obj.Initialize();
            collection.Add(obj);
        }
        sw.Stop();
        Console.WriteLine("Adding " + noObjects + " " + typeof(T).Name + " to a " + collection.GetType().Name + " took " + sw.Elapsed.TotalMilliseconds + " ms");

        if (collection is IList)
        {
            IList list = (IList) collection;
            // access all list objects
            sw.Restart();
            for (int i = 0; i < noObjects; i++)
            {
                var obj = list[i];
            }
            sw.Stop();
            Console.WriteLine("Accessing " + noObjects + " " + typeof (T).Name + " from a List took " + sw.Elapsed.TotalMilliseconds + " ms");
        }
    }

TestClass и TestStruct оба выглядят так (один с 'class', другой с 'struct'):

public class TestClass : ITestThing
{
    public int I1;
    public int I2;
    public double D1;
    public double D2;
    public long L1;
    public long L2;

    public void Initialize()
    {
        D1 = 1;
        D2 = 2;
        I1 = 3;
        I2 = 4;
        L1 = 5;
        L2 = 6;
    }
}

Только TestStruct имеет значение public struct вместо public class и TestClassWithString и TestStructWithString public string S1, которое инициализируется с помощью «abc».

ITestThing просто существует, потому что структуры не могут иметь конструктор, поэтому мне нужен был какой-то способ для вызова метода Initialize () универсальным образом, но оказалось, что если я вызываю Initialize (), это не будет иметь большого значения или нет.

Обратите внимание, что различия в длительности были бы еще более значительными, если бы я написал простой код для каждого тестового примера без какого-либо интерфейса или Activator.CreateInstance, но этот код становился слишком большим, как только я добавил второй тестовый пример ...

РЕЗЮМЕ

Вы можете значительно улучшить свою производительность, используя List с начальным размером и помещая в него Structs, а не экземпляры классов (Objects). Также старайтесь избегать наличия строк в своих структурах, потому что каждый экземпляр String снова является объектом, которого вы пытались избежать, используя Struct вместо объекта.

1 голос
/ 29 июля 2011

Как насчет использования массива вместо List?Вы можете инициализировать его, чтобы иметь начальный размер (скажем, 500000 элементов), и если этого недостаточно, используйте Array.Resize, чтобы добавить еще 100000. Вам просто нужно отслеживать фактическое количество элементов, так как свойство Length будеттолько дать вам количество элементов.

Обратите внимание, однако, что вызов Array.Resize также может занимать много времени, так как в основном будет создан новый массив нового размера и все элементы из исходного массивабудет скопирован в новый массив.Вы не должны называть это слишком часто.

0 голосов
/ 20 октября 2014

Вы пытались дать емкость при инициализации. Таким образом, не нужно перераспределять память и переносить старое содержимое в новое пространство памяти.

List<long> thelist = new List<long>(500000);
0 голосов
/ 29 июля 2011

Вы можете использовать массив, который будет быстрее (но не запрашиваемым). Я не знаю специфики вашего кода, но вы можете использовать рефрактор и использовать базу данных. 500000 предметов никогда не будут быстрыми

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...