选择Distinct Count真的很慢

时间:2016-02-23 15:26:32

标签: c# linq plinq morelinq

我有一个大约7000个对象的循环,在循环中我需要获得一个结构列表的明确计数。目前我正在使用 -

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,它有一个用于Linq的DisctintBy方法,但它没有更高效,因为我已经定时了。我玩过PLinq,但我不确定在哪里并行查询。

所以循环的每次迭代都定时 -
时间流逝:00:00:37.1142047开始
时间流逝:00:00:37.8310148结束

= 0.7168101秒 * 7000 = 5017.6707(83.627845分钟)

它的Distinct()Count()行占用了大部分时间(大约0.5秒)。变量差异有几十万个OrderLineStruct,因此对此进行任何linq查询都很慢。

更新

我已经稍微修改了循环,现在它运行大约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);
}

在Except上有一个.ToList()似乎有所不同,现在我在每次迭代后删除已处理的订单,这会提高每次迭代的性能。

4 个答案:

答案 0 :(得分:2)

你正在错误的地方寻找问题。

orderLinesListdifferencesdifferences.Select(x => x.OrderId).Distinct()只是带有延迟执行的LINQ to Objects链接查询方法,而{ {1}}方法正在执行它们。

您的处理算法非常低效。瓶颈是Count()查询,它会迭代每个orderLinesList的整个totalOrderLines列表,并在productExcept等中进行链接(包含)。 - 再次,在循环内,即7000+次。

以下是IMO执行相同操作的示例高效算法:

Distinct

答案 1 :(得分:1)

我建议您更改LINQ查询的这一部分

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

加入以阅读:

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

这样您只需支付一次费用,而不是让Contains遍历您的产品商店列表7,000次,效率非常低。另外,如果可以使你的id为整数数据类型(int,long)而不是字符串,那么你也应该有更快的搜索和比较。但我想你的模型的结构已经设定了很多。

答案 2 :(得分:1)

在你的案例中,正如Jon Hanna所提到的,瓶颈是Except方法 DistinctCount具有第二优先权 您可以通过对方法的每个部分强制执行枚举并放置秒表来验证这一点。

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可以缩短{p> OrderLineStruct时间

使用明确的GetHashCode

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

没有多余枚举的总时间变化:
默认GetHashCode Total time: 00:00:18.9649790
明确的GetHashCode Total time: 00:00:12.7736320

<强>更新
您也可以通过更改方法逻辑来优化它。

例如,您可以从totalOrderLines创建HashSet,然后只需从中删除项目。

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)时间,这没关系。

答案 3 :(得分:0)

totalOrderLines来自哪里?可能是MSSQL数据库?如果是这样,您必须在OrderId列上有一个索引。在此列上执行不带索引的Distinct()会强制数据库引擎迭代所有行以标识不同的值。