2016-02-23 6 views
3

У меня есть цикл из около 7000 объектов, и в цикле мне нужно получить отчетливое количество списков структур. В настоящее время я использую -Выберите Distinct Count очень медленно

foreach (var product in productsToSearch) 
{ 
    Console.WriteLine("Time elapsed: {0} start", stopwatch.Elapsed); 
    var cumulativeCount = 0; 
    productStore.Add(product); 
    var orderLinesList = totalOrderLines 
     .Where(myRows => productStore.Contains(myRows.Sku)) 
     .Select(myRows => new OrderLineStruct 
     { 
      OrderId = myRows.OrderId, 
      Sku = myRows.Sku 
     }); 
    var differences = totalOrderLines.Except(orderLinesList); 
    cumulativeCount = totalOrderLinsCount - differences.Select(x => x.OrderId).Distinct().Count(); 
    cumulativeStoreTable.Rows.Add(product, cumulativeCount);  
    Console.WriteLine("Time elapsed: {0} end", stopwatch.Elapsed); 
} 

public struct OrderLineStruct 
{ 
    public string OrderId { get; set; } 
    public string Sku { get; set; } 
} 

Это очень медленно при получении отдельного счета. Кто-нибудь знает более эффективный способ сделать это? Я попытался использовать MoreLinq, у которого есть метод DisctintBy для Linq, но он не эффективнее, поскольку я его приурочил. Я играл с PLinq, но я немного не уверен, где распараллелить этот запрос.

Таким образом, каждая итерация цикла приурочен на -
Время истекло: 00: 00: 37,1142047 старт
Время истекло: 00: 00: 37.8310148 конец

= 0.7168101 сек * 7000 = 5017,6707 (83.627845 минут)

Его линия Distinct() Count(), которая занимает больше всего времени для обработки (около 0,5 секунды). Переменные различия имеют несколько сотен тысяч OrderLineStruct, поэтому любые запросы linq на этом медленны.

UPDATE

Я изменил цикл немного, и теперь он работает в около 10 минут, а что в течение 1 часа

foreach (var product in productsToSearch) 
{ 
    var cumulativeCount = 0; 
    productStore.Add(product); 
    var orderLinesList = totalOrderLines 
     .Join(productStore, myRows => myRows.Sku, p => p, (myRows, p) => myRows) 
     .Select(myRows => new OrderLineStruct 
     { 
      OrderId = myRows.OrderId, 
      Sku = myRows.Sku 
     }); 
    totalOrderLines = totalOrderLines.Except(orderLinesList).ToList(); 
    cumulativeCount = totalOrderLinesCount - totalOrderLines.Select(x => x.OrderId).Distinct().Count(); 
    cumulativeStoreTable.Rows.Add(product, cumulativeCount); 
} 

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

+2

Ну, что делает STRUCT выглядеть? [Mcve] действительно помог бы - у нас действительно нет большой информации о том, что происходит в данный момент. –

+0

Сделайте его многопоточным. –

+0

Зная, что такое структура или что означает «действительно медленный» (одна секунда? Десять секунд? Десять минут?) было бы здорово. –

ответ

2

Вы ищите проблему не в этом месте.

orderLinesList, differences и differences.Select(x => x.OrderId).Distinct() только LINQ к объектам прикована запросы методы с отложенного исполнения и методом Count() выполняет их все.

Ваш алгоритм обработки очень неэффективен. Узким местом является запрос orderLinesList, который выполняет итерацию всего списка totalOrderLines для каждого product, и он прикован (в комплекте) в Except, Distinct и т. Д. - снова внутри цикла, т.е. 7000+ раз.

Вот пример эффективный алгоритм, который ИМО делает то же самое:

Console.WriteLine("Time elapsed: {0} start", stopwatch.Elapsed); 
var productInfo = 
(
    from product in productsToSearch 
    join line in totalOrderLines on product equals line.Sku into orderLines 
    select new { Product = product, OrderLines = orderLines } 
).ToList(); 
var lastIndexByOrderId = new Dictionary<string, int>(); 
for (int i = 0; i < productInfo.Count; i++) 
{ 
    foreach (var line in productInfo[i].OrderLines) 
     lastIndexByOrderId[line.OrderId] = i; // Last wins 
} 
int cumulativeCount = 0; 
for (int i = 0; i < productInfo.Count; i++) 
{ 
    var product = productInfo[i].Product; 
    foreach (var line in productInfo[i].OrderLines) 
    { 
     int lastIndex; 
     if (lastIndexByOrderId.TryGetValue(line.OrderId, out lastIndex) && lastIndex == i) 
     { 
      cumulativeCount++; 
      lastIndexByOrderId.Remove(line.OrderId); 
     } 
    } 
    cumulativeStoreTable.Rows.Add(item.Product, cumulativeCount); 
    // Remove the next if it was just to support your processing 
    productStore.Add(item.Product); 
} 
Console.WriteLine("Time elapsed: {0} end", stopwatch.Elapsed); 
+0

Спасибо, Иван, но это не совсем то, что я ищу, но по правильным линиям. Таким образом, у меня есть список продуктов из 7000 продуктов, которые мне нужно перебирать через этот список 1 к 1, добавляя их к некоторому хранилищу (hashset). После каждой итерации я использую hashset, чтобы найти общие порядковые строки, поэтому, например, я буду искать продукт 1 после первой итерации, затем продукт 1 и продукт 2 после второй итерации. Затем мне нужно найти строки порядка из продуктов, затем найти различия между порядковыми строками и totalorderlines (disctinct by order id). Надеюсь, это имеет смысл? –

+0

Это именно то, что делает мой алгоритм, по-другому. В вашем примере вместо DistinctOrderCount (все элементы) - DistinctOrderCount (все элементы, кроме (продукт 1 + продукт2), это DistinctCount (продукт 1 + продукт 2). Разве это не дает тот же результат? –

+0

К сожалению, это не поэтому причина заключается в том, что, например, на первой итерации, если в заказе 1 есть продукт 1 и продукт 2. Ваш алгоритм соответствует продукту 1 и считает его действительным, но мой не будет, поскольку в магазине отсутствует товар 2. Надеюсь, это сделает смысл. –

0

Откуда берутся totalOrderLines? Может быть, база данных MSSQL? Если это так, вам нужно будет иметь индекс в столбце OrderId. Выполнение отличительного() без индекса в этом столбце заставляет механизм БД перебирать все строки для идентификации отдельных значений.

+0

totalOrderLines - это список , который создается из запроса db, поэтому между этим и базой данных нет никакой связи. –

1

Я рекомендую изменить эту часть вашей LINQ запросов

totalOrderLines.Where(myRows => productStore.Contains(myRows.Sku)) 

к Join читать следующим образом:

totalOrderLines.Join(productStore, myRows => myRows.Sku, p => p, (myRows, p) => myRows) 

Таким образом, вы платите стоимость когда-то вместо того, чтобы Contains пройти ваш продукт список магазина 7000 раз, что очень неэффективно. Кроме того, если вы можете создавать свои интегральные типы данных id (int, long), а не строку, вы должны иметь более быстрый поиск и сравнения. Но я предполагаю, что структура вашей модели довольно много.

1

В вашем случае, как упоминал Джон Ханна, узким местом является метод Except.
Distinct и Count имеет второй приоритет.
Вы можете проверить это, применяя перечисление на каждой части вашего метода и помещая секундомер вокруг.

foreach (var product in productsToSearch) 
{ 
    var cumulativeCount = 0; 
    productStore.Add(product); 

    olSw.Start(); 
    var orderLinesList = totalOrderLines 
     .Where(myRows => productStore.Contains(myRows.Sku)) 
     .Select(myRows => new OrderLineStruct 
     { 
      OrderId = myRows.OrderId, 
      Sku = myRows.Sku 
     }).ToList(); 
    olSw.Stop(); 

    exSw.Start(); 
    var differences = totalOrderLines.Except(orderLinesList).ToList(); 
    exSw.Stop(); 

    dcSw.Start(); 
    cumulativeCount = totalOrderLinsCount - differences.Select(x => x.OrderId).Distinct().Count(); 
    dcSw.Stop(); 
} 

Размеры:
productsToSearch счета 100
totalOrderLines счета 300 000

Total olSw time: 00:00:01.3583340
Total exSw time: 00:00:14.3304959
Total dcSw time: 00:00:04.1986018

exSw время может быть уменьшено путем явного осуществления дическим GetHashCode на OrderLineStruct

С явным GetHashCode:

Total olSw time: 00:00:01.4045676
Total exSw time: 00:00:08.4691066
Total dcSw time: 00:00:03.9439711

Общее изменение времени без избыточного перечисления:
По умолчанию GetHashCodeTotal time: 00:00:18.9649790
Явная GetHashCodeTotal time: 00:00:12.7736320

Обновление:
Также вы можете оптимизировать это, изменив логику метода.

Например, вы можете создать HashSet из totalOrderLines, а затем просто удалить элементы из него.

var orderLinesList = totalOrderLines 
    ... 
    .ToList(); 

orderLinesList.ForEach(item => totalOrderLines.Remove(item)); 

cumulativeCount = totalOrderLinsCount - totalOrderLines.Select(x => x.OrderId).Distinct().Count(); 

В моем случае это сокращает общее время до 7 секунд.
Total time: 00:00:07.0851111

В этом случае перечисление через TotalOrderLines с Dictinct является узким местом, но это занимает O(N) время, которое хорошо.

+0

У вас есть более быстрый способ сравнить два списка и выбрать различия, а не использовать Except? –

+0

Боюсь, что нет. В соответствии с этим ответом http://stackoverflow.com/a/2799543/3872935 «Исключить» метод достаточно хорош. – Kote

+0

Вы можете создать 'HashSet' из' totalOrderLines', а затем просто удалить элементы из набора 'orderLinesList.ForEach (item => totalOrderLines.Remove (item));' вместо использования 'Except'. Он будет работать быстрее, но это логическое изменение. – Kote