【问题标题】:How to speed up the comparison of many fields using IEqualityComparer?如何使用 IEqualityComparer 加快多个字段的比较?
【发布时间】:2019-02-01 08:40:38
【问题描述】:

我在数据库中有一个AE_AlignedPartners 项目列表,我使用以下方法检索:

List<AE_AlignedPartners> ae_alignedPartners_olds = ctx.AE_AlignedPartners.AsNoTracking().ToList();

然后,我得到并使用 JSON 序列化了一个新列表(具有相同的对象类型):

List<AE_AlignedPartners> ae_alignedPartners_news = GetJSONPartnersList();

比我得到两者的交集:

var IDSIntersections = (from itemNew in ae_alignedPartners_news
                        join itemOld in ae_alignedPartners_olds on itemNew.ObjectID equals itemOld.ObjectID
                        select itemNew).Select(p => p.ObjectID).ToList();

现在,由于这些交叉点,我需要检查某些记录是否已更改,检查许多字段,然后添加到“监控”更新列表中。代码如下:

IList<AE_AlignedPartners> ae_alignedPartners_toUpdate = new List<AE_AlignedPartners>();
foreach (var item in IDSIntersections)
{
    var itemOld = ae_alignedPartners_olds.First(p => p.ObjectID == item);
    var itemNew = ae_alignedPartners_news.First(p => p.ObjectID == item);

    if (itemOld.Field1 != itemNew.Field1 ||
        itemOld.Field2 != itemNew.Field2 ||
        itemOld.Field3 != itemNew.Field3 ||
        itemOld.Field4 != itemNew.Field4 ||
        itemOld.Field5 != itemNew.Field5 ||
        itemOld.Field6 != itemNew.Field6 ||
        itemOld.Field7 != itemNew.Field7 ||
        itemOld.Field8 != itemNew.Field8 ||
        itemOld.Field9 != itemNew.Field9)
    {
        AE_AlignedPartners toUpdate = mapper.Map<AE_AlignedPartners, AE_AlignedPartners>(itemNew);
        toUpdate.ID = itemOld.ID;

        ae_alignedPartners_toUpdate.Add(toUpdate);
    }
}

这非常慢(大约 4 分钟发布,大约 7 万条记录)。

最近发现hereIEqualityComparer,确实加快了比较的进程。

在这种情况下我可以利用它吗?或者哪些是有效的优化?

我不会使用建议的FullOuterJoin,因为这意味着现在需要进行大量重构(我会在下一个项目中进行,保证)。

有什么建议吗?谢谢

【问题讨论】:

  • 假设是相等比较特别导致了性能问题似乎是没有根据的。您是否对其进行了分析以查看实际花费最多的时间?从实际的 DB I/O 到过多的内存分配,再到对大型集合进行线性搜索的重复 .First() 调用,这些比较可能会在我的可能候选列表中下降。
  • 你已经实现了 嵌套循环 foreach (...) {..First(...)}: 所以你有 O(N ** 2) 时间复杂度 70k * 70k ~ 5e9 循环很慢。让我们把ae_alignedPartners_oldsae_alignedPartners_news 变成一个带有ObjectID 键的字典
  • @DmitryBychenko:你能举个例子吗?
  • ObjectIDint?
  • @Rango: 不是字符串

标签: c# .net performance linq


【解决方案1】:

你有嵌套循环实现

// O(N) : Loop over IDSIntersections
foreach (var item in IDSIntersections)
{
    // O(N) : Again, loop over ae_alignedPartners_olds
    var itemOld = ae_alignedPartners_olds.First(p => p.ObjectID == item);
    var itemNew = ae_alignedPartners_news.First(p => p.ObjectID == item);
    ...

在最坏的情况下,您将拥有O(N) * O(N) = O(N**2) 时间复杂度;数十亿个循环:70k * 70k ~ 5e9。让我们在 dictionaries 的帮助下摆脱 inner loops

// O(N)
var dictOld = ae_alignedPartners_olds
  .GroupBy(p => p.ObjectID) // ObjectID should be a int, string or provide good GetHashCode()
  .ToDictionary(chunk => chunk.Key,
                chunk => chunk.First());

// O(N)
var dictNew = ae_alignedPartners_news
  .GroupBy(p => p.ObjectID) 
  .ToDictionary(chunk => chunk.Key,
                chunk => chunk.First());

// O(N)
foreach (var item in IDSIntersections)
{
   // O(1) : no loops when finding value by key in dictionary
   var itemOld = dictOld[item];      
   var itemNew = dictNew[item];
   ... 

现在我们将有大约3 * O(N) 循环:3 * 70k ~ 2e5

【讨论】:

    【解决方案2】:

    自定义IEqualityComparer&lt;AE_AlignedPartners&gt; 会很好,但不是因为它提高了性能,它需要进行相同的比较。但是因为封装了那里的逻辑使它更易于维护、可读和可重用。您可以将它用于许多 LINQ 方法。

    缓慢的是您总是在foreach-loop 中通过ObjectId 搜索旧项目和新项目。

    你不需要选择两者共同的ObjectID,如果你已经加入了新旧,只需将整个实例存储在一个匿名类型中:

    var intersections = from itemNew in ae_alignedPartners_news
                        join itemOld in ae_alignedPartners_olds on itemNew.ObjectID equals itemOld.ObjectID
                        select new { New = itemNew, Old = itemOld };
    
    foreach(var x in intersections)
    {
        var itemOld = x.Old;
        var itemNew = x.New;
        // ... 
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-27
      • 2022-01-01
      • 1970-01-01
      • 2011-10-30
      相关资源
      最近更新 更多