C# 如何将列表的子集添加到另一个列表中对象的属性-最佳/最快实践

C# 如何将列表的子集添加到另一个列表中对象的属性-最佳/最快实践,c#,performance,linq,parallel.foreach,concurrentdictionary,C#,Performance,Linq,Parallel.foreach,Concurrentdictionary,我试图找到一种最快、最有效的方法,根据一个键属性从列表中选择一个项目子集,并将这个子集列表分配给另一个列表中的项目属性。性能方面很重要,因为这部分代码在每个工作日都会被频繁调用。我用滴答声来测量性能,以清楚地看到相对差异 我有两个列表示例设置 List<CategorySetting> catList; List<Customer> custList; 这将需要大约13275个滴答声 然后我想,也许使用并行可以快得多?所以我写了这段代码 // parallel for

我试图找到一种最快、最有效的方法,根据一个键属性从列表中选择一个项目子集,并将这个子集列表分配给另一个列表中的项目属性。性能方面很重要,因为这部分代码在每个工作日都会被频繁调用。我用滴答声来测量性能,以清楚地看到相对差异

我有两个列表示例设置

List<CategorySetting> catList;
List<Customer> custList;
这将需要大约13275个滴答声

然后我想,也许使用并行可以快得多?所以我写了这段代码

// parallel for each: 82541 ticks
Parallel.ForEach(catList, catItem =>
{
   catItem.Customers = custList.Where(c => c.SettingsId == catItem.SettingsId).ToList();
});
这需要更长的时间;82541滴答声。这对我来说毫无意义,因为这种方法具有并行性。它应该使用多个线程来实现这一点,因此理论上应该快得多。然后我开始思考如果多个线程同时尝试访问customerlist会发生什么。这可能会导致锁和队列,因此需要更多的时间,因为开销?与写入主列表的操作相同

我尝试了另一种方法。我为catList主列表制作了一个ConcurrentBag

ConcurrentBag<CategorySettings> csBag = new ConcurrentBag<CategorySettings>(catList);
最后的尝试是这样的:

// paralell, bag, concurrent dictionary: 40255
Parallel.ForEach(caBag, ca =>
{   
   concDict.TryGetValue(ca.SettingsId, out var selCust);
   ca.Customers = selCust;
});
这将需要40255个刻度。有人能解释为什么这仍然需要更长的时间吗?更重要的是,除了“仅仅”一个foreach循环之外,没有其他方法了吗?感觉好像我错过了什么

任何想法都非常感谢

您可以尝试使用LINQ方法:

var customersLookup = custList.ToLookup(item => item.SettingsId);

foreach (var catItem in catList)
{
    catItem.Customers = customersLookup[catItem.SettingsId].ToList();
}

我假设CategorySetting类具有IList类型的可写属性Customers。如果属性是IEnumerable类型,您可以省略ToList调用。

我最终使用了我拥有的

catList中的foreach变量catItem { catItem.Customers=custList.c=>c.SettingsId==catItem.SettingsId.ToList; }


@NetMage很好地指出,这可能是无用的优化。我认为并行foreach将在这里发挥主要作用,但无论如何,第一个循环仍然是最快的。在我的quadcore和xeon上。

以我的经验,Parallel.ForEach的速度明显低于常规的速度,除非你在处理数十万个项目。即使这样,情况也不会好很多。在回答这个问题时,有人能解释为什么[Parallel.ForEach]仍然比[ForEach]花费更长的时间吗?作为复制品。@TheodorZoulias重新打开。快回答!当我进行测试时,我发现Parallel.ForEach的速度是.NETCore3的两倍,ForEach 200个设置ID,1846个客户,每个4-10个,大约有20000个刻度。我认为并行的影响真的取决于你的CPU-我有4个内核为8个逻辑CPU超线程。13275滴答大约是1.3毫秒-你每天运行多少次?即使你把它降到零,这也只是一天的1/6500——你必须每两秒运行一次,以节省一天中的一分钟。你确定这不是一个过早的优化吗?请注意,foreach循环的周期为13275次,ToLookup的开销会使优化速度大大降低,除非在catItem更新之间custList不会改变。此外,使用GroupBy+ToDictionary设置字典,例如custList.GroupByitem=>item.SettingsId.ToDictionary=>g.Key,g=>g.ToList;比在foreach循环中执行ToList要快,假设您需要一个列表。这具有On²计算复杂性,因此它应该非常快或非常慢,具体取决于custList的大小。小列表=>快速。大列表=>慢。客户列表总是355项或更少。再也不会了。谢谢你的解释。这让我想起了我知道的是多么少:100-200个条目通常是散列方法开始超越蛮力循环的临界点。因此,我猜对于给定的列表最大大小,您可以选择解决方案。
// paralell, bag, concurrent dictionary: 40255
Parallel.ForEach(caBag, ca =>
{   
   concDict.TryGetValue(ca.SettingsId, out var selCust);
   ca.Customers = selCust;
});
var customersLookup = custList.ToLookup(item => item.SettingsId);

foreach (var catItem in catList)
{
    catItem.Customers = customersLookup[catItem.SettingsId].ToList();
}