【问题标题】:Is there a more efficient way to reconcile large data sets?有没有更有效的方法来协调大型数据集?
【发布时间】:2025-11-27 17:45:02
【问题描述】:

我的任务是协调两个大数据集(两个大交易列表)。基本上,我将两个数据源中的相关字段提取到两个相同格式的文件中,然后比较这些文件以查找 A 中但不在 B 中的任何记录,反之亦然,并报告它们。我在my best efforts achieving this (click if interested).写了一篇博文

它的要点是将两个数据集加载到一个大哈希表中,键是行,每次出现在文件 A 中时值为 +1,每次出现在文件 B 中时值为 -1 . 然后在最后,我寻找任何值 != 0 的键/值对。

我的算法似乎足够快(2*100mb 文件需要 10 秒),但是它有点占用内存:280mb 比较两组 100mb 文件,我希望将其降低到 100mb 峰值内存使用量,并且可能如果两个数据集的排序大致相同,则较低。

有什么想法吗?

另外,如果这对 SO 来说过于开放,请告诉我。

【问题讨论】:

    标签: c# optimization memory comparison


    【解决方案1】:

    我能想到的唯一方法是不要一次将所有数据加载到内存中。如果你改变处理它的方式,让它一次抓取每个文件的一部分,它会减少你的内存占用,但会增加你的磁盘 IO,这可能会导致更长的处理时间。

    【讨论】:

    • 我准备牺牲一些处理时间来减少内存使用。知道如何不一次将至少一个数据集的所有数据加载到内存中吗?这可能很困难,因为这两个数据集的顺序不同。
    • 不知道知道不重要,但是你引用的内存,是不是有很大的不匹配的时候(数据重叠很少,所以报告差不多大小 A.txt + B.txt) 或当有一个大匹配?如果是大型匹配,并且您不关心报表中的匹配数据,您可以尝试在找到匹配项后立即删除匹配项。您可以尝试在内存数据结构中删除。此外,您可以尝试从文件中删除匹配的项目。
    • 存在非常低的不匹配 - 例如,可能有几十个记录不匹配。那么您是否建议在我加载第二个文件时从哈希表中删除匹配的键?我想我已经尝试过了,但内存使用量并没有下降太多。但是,这可能是因为 GC 收集的频率不高。
    【解决方案2】:

    一种选择可能是更改数据的内存格式。如果您的数据是以文本形式存储的一系列数字,则将它们以整数形式存储在内存中可能会减少您的内存占用。

    另一种选择可能是使用某种外部程序对行进行排序——然后您可以按顺序对两个文件进行简单扫描以查找差异。

    回到您的问题,不过,比较一对 100mb 的文件,280mb 听起来很高——您只是将一个加载到内存中(较小的一个),然后滚动浏览另一个,对吗?正如您所描述的那样,我认为您不需要同时将两者的全部内容都保存在内存中。

    【讨论】:

    • 我喜欢使用另一个程序预先对行进行排序的想法。这肯定会简化比较过程。但是您能想出一种方法来对本身使用较少内存的行进行排序吗?
    • 我已经使用 DOS 排序命令进行了测试:每个文件需要 10 秒,并且使用 225MB 内存。所以内存使用情况与我的解决方案非常相似。但我认为你在做某事。
    • 合并排序的好处在于,理论上,您可以通过两遍系统选择内存限制——您可以读取 X 行,对它们进行排序,然后将它们写入 file1,对文件的其余部分重复。然后,您同时读回所有文件,写出最低行并仅推进该文件。哎呀,如果你设置了一个 IEnumerable 实现来读取文件,以及一个需要 N IEnumerables 的 MergeSort,让它工作起来应该相对简单。
    • 现在我看到了 Chris 对 Kevin 的回答的评论——显然这被称为“外部排序”。有道理。
    • 是的,您刚刚描述了一个“外部合并排序”。嘿,两个人想出类似的解决方案并没有什么坏处,这是一个很好的指标,如果有的话,这是一个很好的解决方案。
    【解决方案3】:

    我只在使用 shell 和 perl 的 unix 脚本中做了类似的事情,但是理论可能会发生变化。

    第 1 步,对两个文件进行排序,使它们按照相同的标准排列。我使用 unix 排序命令来执行此操作(我需要唯一标志,但您只需要某种内存高效的文件排序)。这可能是您自己弄清楚的棘手部分。

    第 2 步,打开两个文件,基本上逐行扫描它们(如果是二进制格式,则逐条记录)。如果左侧文件中的行与右侧文件中的行相等,则这些行匹配并继续前进(记住我们已经对文件进行了排序,所以最小的记录应该排在第一位)。

    如果左侧记录大于右侧记录,则缺少右侧记录,将其添加到列表中,然后读取右侧文件的下一行。只是你是否再次检查。如果您的右侧记录大于左侧记录,则同样适用,请报告并继续。

    扫描记录应该非常节省内存。它可能没有那么快,但对我来说,我能够在几分钟内通过多次查看不同字段来处理几场数据。

    【讨论】:

    • 好计划。我认为你是对的,这会很好地扩展,除了排序命令可能是棘手的部分。
    • 是的,我会看看 unix 排序是如何工作的,因为它使用临时文件并且对于排序文本文件的效率似乎并不算太​​糟糕。您可以在字段提取过程中进行排序。
    • 刚刚找到这个,这是用于对大于 ram 的数据集进行排序的方法的名称:en.wikipedia.org/wiki/External_sorting 因此,结合您的方法,听起来它会产生一种高度可扩展的比较/协调方法.
    • 好的,我刚刚以不占用太多 RAM 的方式实现了外部合并排序,可以在这里看到:splinter.com.au/blog/?p=142
    • 嘿,很高兴你解决了它。外部排序,非常有趣,很高兴您发送了链接。我认为您可能发现拆分是最慢操作的原因是,您可能从磁盘读取文件,但是当您读取拆分文件时,窗口可能仍然在 ram 中有一个副本,正在读取返回,这意味着您只是在写入磁盘,而不是读取和写入。
    【解决方案4】:

    使用此方法,您必须始终将其中一个文件的内容保存在内存中。就内存而言,只取一半文件会更有效。将它与第二个文件逐行比较。然后记住下半部分并做同样的事情。这种重叠将确保不会丢失任何记录。并且将消除临时存储整个文件的需要。

    【讨论】:

    • 此外,如果您需要使用更少的内存,您可以简单地将文件的前三分之一与第一个进行比较,然后将文件的第二个三分之一与第一个进行比较,等等。显然会有一个随着时间的推移,为了增加内存而牺牲了程序速度。
    • 这听起来与凯文的回答相似,但他只说一次读入一行而不是文件的一半。结合外部排序,这是协调大数据集的方法。
    • 他建议的方式不适用于大型数据文件。他的方法是在外部对两个文件进行排序(如我的),然后打开两个文件(与我的不同)并逐行比较它们。并且计算机将没有足够的内存来执行此操作。我建议打开一个文件的一半,然后将该文件逐行与第二个文件进行比较。因此,一次只使用一个文件的一半(或更少)。 Kevin 的方法是同时打开两个文件(至少是内存量的 4 倍)
    最近更新 更多