如何使用IEqualityComparer加快许多字段的比较?

时间:2019-02-01 08:40:39

标签: c# .net performance linq

我在数据库中有一个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万条记录。)

最近我发现了here IEqualityComparer,它确实加快了比较的过程。

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

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

有什么提示吗?谢谢

2 个答案:

答案 0 :(得分:3)

您有嵌套循环实现

// 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 摆脱内部循环

// 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

答案 1 :(得分:1)

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

比较慢的是,您总是通过ObjectId循环中的foreach搜索旧项目和新项目。

如果您已经加入了新旧版本,则无需同时选择两者的公用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;
    // ... 
}