C# 使用替代解决方案提高循环性能

C# 使用替代解决方案提高循环性能,c#,.net,linq,C#,.net,Linq,我有两个列表对象,产品和价格 var products = GetProdusts(predicate); var prices = GetAllProductPrices(); 用于填充产品。带价格对象的价格: Parallel.ForEach(products ,prod=> { prod.Prices= prices?.Where(pr =>pr.ProductId == prod.Id)?.ToList(); }); 这个循环需要很长时间。 谁能帮我改进一下吗 更

我有两个列表对象,产品和价格

var products = GetProdusts(predicate);
var prices = GetAllProductPrices();
用于填充产品。带价格对象的价格:

Parallel.ForEach(products ,prod=> {

   prod.Prices= prices?.Where(pr =>pr.ProductId == prod.Id)?.ToList();
});
这个循环需要很长时间。 谁能帮我改进一下吗

更新:

基于@Holger评论:

var priceDics = prices.GroupBy(p=>p.ProductId).ToDictionary(p=>p.Key ,p=>p.ToList());

Parallel.ForEach(products ,prod=> {

  prod.Prices= priceDics?.Where(pr =>pr.Key== prod.Id)?.SelectMany(x=>x.Value).ToList();
});

现在性能得到了很大的提高。

这里有一个简单的解决方案,它使用。它应该是如此之快,以至于并行不能使它更快

var lookup = prices.ToLookup(p => p.ProductId);
foreach (var product in products)
{
    product.Prices = lookup[product.Id]?.ToList();
}
在循环中没有太多的计算。
查找[…]
getter几乎是即时的,剩下的是为列表分配内存,并将每个列表的内容复制到新的
列表中,这是一种高效的单CPU指令操作


更新:使用500000种产品和11000000种价格进行性能测试:

class Price { public int ProductId; }
class Product { public int Id; public List<Price> Prices; }

var products = Enumerable.Range(1, 500_000)
    .Select(n => new Product() { Id = n }).ToList();
var prices = Enumerable.Range(1, 11_000_000)
    .Select(n => new Price { ProductId = n % products.Count }).ToList();

var stopwatch = Stopwatch.StartNew();
var lookup = prices.ToLookup(p => p.ProductId);
Console.WriteLine($"Duration Lookup: {stopwatch.ElapsedMilliseconds:#,0} msec");
foreach (var product in products)
{
    product.Prices = lookup[product.Id]?.ToList();
}
Console.WriteLine($"Duration Total: {stopwatch.ElapsedMilliseconds:#,0} msec");
class Price{public int ProductId;}
类产品{public int Id;public List Prices;}
var产品=可枚举范围(1500_000)
.Select(n=>newproduct(){Id=n}).ToList();
var价格=可枚举范围(1,11_000_000)
.Select(n=>newprice{ProductId=n%products.Count});
var stopwatch=stopwatch.StartNew();
var lookup=prices.ToLookup(p=>p.ProductId);
WriteLine($“持续时间查找:{stopwatch.elapsedmillesons:#,0}毫秒”);
foreach(产品中的var产品)
{
product.Prices=lookup[product.Id]?.ToList();
}
Console.WriteLine($“持续时间总计:{stopwatch.ElapsedMilliseconds:#,0}毫秒”);
输出:

查找持续时间:4051毫秒
总持续时间:4695毫秒


进程最慢的部分是填充
查找
,这是不可并行的。为产品分配价格的最后一个循环可以并行化,但只需不到一秒钟。

这里有一个简单的解决方案,它使用。它应该是如此之快,以至于并行不能使它更快

var lookup = prices.ToLookup(p => p.ProductId);
foreach (var product in products)
{
    product.Prices = lookup[product.Id]?.ToList();
}
在循环中没有太多的计算。
查找[…]
getter几乎是即时的,剩下的是为列表分配内存,并将每个列表的内容复制到新的
列表中,这是一种高效的单CPU指令操作


更新:使用500000种产品和11000000种价格进行性能测试:

class Price { public int ProductId; }
class Product { public int Id; public List<Price> Prices; }

var products = Enumerable.Range(1, 500_000)
    .Select(n => new Product() { Id = n }).ToList();
var prices = Enumerable.Range(1, 11_000_000)
    .Select(n => new Price { ProductId = n % products.Count }).ToList();

var stopwatch = Stopwatch.StartNew();
var lookup = prices.ToLookup(p => p.ProductId);
Console.WriteLine($"Duration Lookup: {stopwatch.ElapsedMilliseconds:#,0} msec");
foreach (var product in products)
{
    product.Prices = lookup[product.Id]?.ToList();
}
Console.WriteLine($"Duration Total: {stopwatch.ElapsedMilliseconds:#,0} msec");
class Price{public int ProductId;}
类产品{public int Id;public List Prices;}
var产品=可枚举范围(1500_000)
.Select(n=>newproduct(){Id=n}).ToList();
var价格=可枚举范围(1,11_000_000)
.Select(n=>newprice{ProductId=n%products.Count});
var stopwatch=stopwatch.StartNew();
var lookup=prices.ToLookup(p=>p.ProductId);
WriteLine($“持续时间查找:{stopwatch.elapsedmillesons:#,0}毫秒”);
foreach(产品中的var产品)
{
product.Prices=lookup[product.Id]?.ToList();
}
Console.WriteLine($“持续时间总计:{stopwatch.ElapsedMilliseconds:#,0}毫秒”);
输出:

查找持续时间:4051毫秒
总持续时间:4695毫秒


进程最慢的部分是填充
查找
,这是不可并行的。为产品分配价格的最后一个循环可以并行化,但只需不到一秒钟。

将一个集合转换为字典,然后迭代另一个集合。也许可以按ProductID对价格进行分组。您可以创建查找,这是分组和字典的良好组合。在生成的列表中,您应该提前创建(作为空列表),并逐个添加元素。因此,您可以避免这种嵌套迭代。这种情况不保证并行性。开销可能会超过收益。你只需要使用一个有效的数据结构()。@TheodorZoulias我测试了查找和字典,对我来说字典比查找快,当我使用并行循环时需要1分钟,没有并行循环时需要+5分钟。为什么不使用连接?您可能只得到一行结果,而且您没有一百万数据,速度可能更快。很难相信使用
查找
而不使用并行性需要5分钟。不应该超过一秒钟。你有多少产品和产品价格?将你的一个收藏转换成一本字典,然后迭代另一个收藏。也许可以按ProductID对价格进行分组。您可以创建查找,这是分组和字典的良好组合。在生成的列表中,您应该提前创建(作为空列表),并逐个添加元素。因此,您可以避免这种嵌套迭代。这种情况不保证并行性。开销可能会超过收益。你只需要使用一个有效的数据结构()。@TheodorZoulias我测试了查找和字典,对我来说字典比查找快,当我使用并行循环时需要1分钟,没有并行循环时需要+5分钟。为什么不使用连接?您可能只得到一行结果,而且您没有一百万数据,速度可能更快。很难相信使用
查找
而不使用并行性需要5分钟。不应该超过一秒钟。你们有多少种产品和产品价格?