Подтвердить что ты не робот

Список С# <> Добавить()

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

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

Я пробовал различные трюки (инициализация коллекции с определенным размером - 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 пустыми прогнозами.

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

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

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

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

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

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

4b9b3361

Ответ 1

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

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

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

Ответ 2

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

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

Ответ 3

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

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

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

Кроме того, посмотрите http://www.simple-talk.com/dotnet/performance/the-top-5-.net-memory-management-misconceptions/, я думаю, что это поможет вам.

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

Ответ 4

Проблема не имеет ничего общего с производительностью 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.

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

Ответ 5

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

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

Ответ 6

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

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

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

Я взял разные структуры данных и разместил в них 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 выглядят следующим образом (один с "классом", один с "структурой" ):

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, но этот код стал слишком большим, как только я добавил второй тестовый пример...

СУЩНОСТЬ

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

Ответ 7

Вы пытались дать возможность инициализации. Поэтому нет необходимости перераспределять память и переносить старое содержимое в новое пространство памяти.

List<long> thelist = new List<long>(500000);

Ответ 8

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