Select Distinct Count 真的很慢
Select Distinct Count is really slow
我有一个包含大约 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() 似乎有所不同,现在我在每次迭代后删除已处理的订单,这会提高每次迭代的性能。
totalOrderLines 来自哪里?也许是 MSSQL 数据库?如果是这样,您将必须在 OrderId 列上有一个索引。在此列上执行没有索引的 Distinct() 会强制数据库引擎遍历所有行以识别不同的值。
我建议更改 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)而不是字符串,您也应该有更快的搜索和比较。但我猜你的模型结构已经差不多定型了。
在你的情况下,正如 Jon Hanna 提到的,瓶颈是 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
时间可以通过在 OrderLineStruct
显式实现 GetHashCode
来减少
显式 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)
时间,这没关系。
你找错地方了
orderLinesList
、differences
和 differences.Select(x => x.OrderId).Distinct()
只是 LINQ to Objects 链接 query 方法 延迟执行,Count()
方法正在执行它们。
您的处理算法非常低效。瓶颈是 orderLinesList
查询,它为每个 product
迭代整个 totalOrderLines
列表,并且它被链接(包含)在 Except
、Distinct
等中。 - 再次,在循环内,即 7000+ 次。
这是 IMO 做的相同的示例高效算法:
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);
我有一个包含大约 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() 似乎有所不同,现在我在每次迭代后删除已处理的订单,这会提高每次迭代的性能。
totalOrderLines 来自哪里?也许是 MSSQL 数据库?如果是这样,您将必须在 OrderId 列上有一个索引。在此列上执行没有索引的 Distinct() 会强制数据库引擎遍历所有行以识别不同的值。
我建议更改 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)而不是字符串,您也应该有更快的搜索和比较。但我猜你的模型结构已经差不多定型了。
在你的情况下,正如 Jon Hanna 提到的,瓶颈是 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
时间可以通过在 OrderLineStruct
GetHashCode
来减少
显式 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)
时间,这没关系。
你找错地方了
orderLinesList
、differences
和 differences.Select(x => x.OrderId).Distinct()
只是 LINQ to Objects 链接 query 方法 延迟执行,Count()
方法正在执行它们。
您的处理算法非常低效。瓶颈是 orderLinesList
查询,它为每个 product
迭代整个 totalOrderLines
列表,并且它被链接(包含)在 Except
、Distinct
等中。 - 再次,在循环内,即 7000+ 次。
这是 IMO 做的相同的示例高效算法:
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);