检测重复文件的最快算法

时间:2018-11-15 08:05:44

标签: algorithm performance hash duplicates

在我2 TB的HDD存储映像中查找重复项的过程中,我对fslint和fslint-gui工具的长时间运行感到惊讶。
因此,我分析了核心工具findup的内部结构,该核心工具使用超长管道实现为编写和记录良好的shell脚本。本质上,它基于查找和哈希(md5和SHA1)。 作者指出,它比我不敢相信的任何其他选择都要快。因此,我发现Detecting duplicate files的话题很快就转向了散列和比较散列,这在我看来并不是最好和最快的方法。

所以通常的算法似乎是这样工作的:

  • 生成所有文件的排序列表(路径,大小,id)
  • 将文件大小完全相同
  • 计算具有相同大小的所有文件的哈希并比较哈希
  • 相同意味着文件相同-找到重复文件

有时,速度会提高,首先使用具有更高冲突概率的更快的哈希算法(例如md5),然后如果哈希值相同,则使用第二个较慢但类似冲突的算法来证明重复项。另一个改进是,首先仅哈希一小部分即可整理出完全不同的文件。

因此,我认为该方案在两个不同的方面被打破了:

  • 重复的候选者从慢速硬盘中读取(第一个块),然后再次读取(完整的md5),然后再次(sha1)读取
  • 通过使用散列而不是逐字节比较文件,我们引入了(低)假阴性的可能性
  • 哈希计算比逐字节比较要慢得多

我找到了一个(Windows)应用程序,该应用程序通过不使用这种常见的哈希方案来实现快速运行。

我的想法和观点完全错误吗?

[更新]

似乎有人认为散列可能比比较快。但这似乎是对“哈希表加快速度”的一般用法的误解。但是要在第一次需要完全逐字节读取文件时生成文件的哈希。因此,一方面是逐字节比较,它仅比较每个重复候选函数的这么多字节,直到第一个不同的位置。还有一个散列函数,它从那么多字节中生成一个ID-假设一个TB的前10k字节,如果前10k相同,则说完整TB。因此,在我通常不具有所有文件哈希值的现成的可计算且自动更新的表的假设下,我需要计算哈希值并读取重复候选者的每个字节。逐字节比较不需要这样做。

[更新2]

我得到了第一个答案,该答案再次朝着以下方向发展:“哈希通常是个好主意”,出于这个想法(不是很错误),他试图通过合理的参数(IMHO)来合理地使用哈希。问题不是“更好或更快的哈希值,因为您可以稍后再使用它们”。 “假设许多(假设n个)文件大小相同,要查找重复的文件,则需要进行n *(n-1)/ 2个比较,以成对的方式对它们进行相互测试。您只需要对每个哈希进行一次哈希处理,就可以为您提供总共n个哈希。”也偏爱哈希和错误(IMHO)。为什么不能只从每个相同大小的文件中读取一个块并在内存中进行比较?如果必须比较100个文件,则可以打开100个文件句柄,并从每个文件句柄中并行读取一个块,然后在内存中进行比较。这似乎比用这100个文件更新一个或多个复杂的慢速散列算法快得多。

[更新3]

考虑到存在很大的偏见,即“应该总是使用哈希函数,因为它们非常好!”我通读了一些关于哈希质量的问题,例如这个: Which hashing algorithm is best for uniqueness and speed?似乎是由于常见的散列函数更经常产生冲突,然后我们认为,由于设计不当和生日paradoxon,我们会认为这是冲突的原因。测试集包含:“ 216,553个英语单词的列表(小写), 数字“ 1”到“ 216553”(请考虑邮政编码,以及不良哈希如何使msn.com崩溃)和216553“随机”(即4型uuid)GUID”。这些微小的数据集从大约100到大约20k产生因此,仅基于哈希测试(不)相等的数百万个文件可能根本不是一个好主意。

我想我需要修改1并将管道的md5 / sha1部分替换为“ cmp”并仅测量时间。我会及时通知您。

[更新3] 感谢您的反馈。我们正在慢慢地转换。背景是我在机器md5上运行fslints查找时发现数百张图像时所观察到的背景。花费了一段时间,HDD旋转得像地狱一样。因此,我在徘徊:“这个疯狂的工具到底在破坏我的硬盘并在逐字节比较时花费大量时间思考着什么呢?”是1)每字节比任何哈希或校验和算法便宜,以及2)带有一个逐字节比较我可以在第一个差异上尽早返回,因此通过读取完整文件并计算完整文件的哈希值,我节省了不浪费HDD带宽和时间的大量时间。我仍然认为这是对的-但是:我想我没有明白1:1比较(如果(file_a [i]!= file_b [i])返回1;)可能比按字节散列便宜。但是,当需要比较多个文件时,使用O(n)进行复杂度明智的哈希运算可能会获胜。我已经在列表上设置了这个问题,并计划用cmp替换findup的fslint的md5部分或增强pythons filecmp.py compare lib,它一次只能比较2个文件,并带有多个文件选项,也许是md5hash版本。 所以,谢谢大家。 通常情况就像你们说的那样:最佳方法(TM)完全取决于以下情况:HDD与SSD,相同长度文件的可能性,重复文件,典型文件大小,CPU与内存与磁盘的性能,单个与多核等等。而且我了解到我应该更多地考虑使用哈希-但是我是一个嵌入式开发人员,大多数时候资源非常有限;-)

感谢您的努力! 马塞尔

3 个答案:

答案 0 :(得分:3)

最快的重复数据删除算法将取决于几个因素:

  1. 发现近重复项的频率如何?如果非常频繁地查找数百个内容完全相同且相差一个字节的文件,则强哈希处理将更具吸引力。如果很难找到一对以上大小相同但内容不同的文件,则可能不需要散列。
  2. 从磁盘读取数据有多快?文件有多大?如果从磁盘读取数据非常慢或文件很小,则单遍哈希(无论密码学如何强大)将比通过弱哈希进行小遍然后通过仅当弱哈希匹配时进行更强遍过更快。 li>
  3. 您打算运行该工具多少次?如果您打算多次运行它(例如,要不断进行重复数据删除),那么使用每个文件的路径,大小和strong_hash构建索引可能是值得的,因为您会无需在以后运行该工具时重建它。
  4. 您要检测重复的文件夹吗?如果要这样做,可以构建一个Merkle tree(本质上是文件夹内容及其元数据的递归哈希);并将这些哈希也添加到索引中。
  5. 您如何处理文件许可权,修改日期,ACL和其他不包含实际内容的文件元数据?这与算法速度没有直接关系,但是在选择如何处理重复项时会增加额外的复杂性。

因此,没有任何一种方法可以回答原始问题。什么时候最快?

假设两个文件的大小相同,通常没有最快的方法来检测它们是否重复,而不是逐字节比较它们(即使从技术上讲,您会逐块比较它们,因为读取块时文件系统比单个字节更有效。

假设许多(例如n)文件的大小相同,要查找重复的文件,您需要进行n * (n-1) / 2比较以成对进行测试彼此对抗。使用强哈希,您只需要对每个哈希进行一次哈希处理即可,总共获得n个哈希。即使进行散列操作比逐字节比较花费k倍,当使用k > (n-1)/2时,散列效果更好。哈希可能会产生假阳性(尽管强哈希只会在天文概率较低的情况下才这样做),但是逐字节进行测试只会使k最多增加1。使用k=3,您将n>=7领先一步;如果您使用更保守的k=2,则可以使用n=3达到收支平衡。实际上,我希望k非常接近1:从磁盘读取数据可能比对已读取的内容进行哈希处理更为昂贵。

几个文件具有相同大小的概率随文件数量的平方增加(查找生日悖论)。 因此,在一般情况下,可以预期哈希是一个很好的主意。万一您再次运行该工具,它也可以极大地提高速度,因为它可以重用现有索引而不是构建它重新。因此,将1个新文件与1M个现有的,不同大小的,相同大小的索引文件进行比较,可以预期在索引中进行1次哈希+ 1次查找,而在无哈希,无索引的情况下进行1M次比较:估计有1M次快点!

请注意,您可以使用多级散列重复相同的参数:如果您使用非常快的散列,例如第一个,中央和最后1k个字节,则将是 比比较文件更快(上面的{k < 1)进行哈希处理-但您会遇到冲突,并且在发现哈希值和/或逐字节比较的情况下进行第二次传递。这是一个折衷:您押注会有一些差异,这些差异将为您节省完整散列或完整比较的时间。我认为总体上值得,但是“最佳”答案取决于计算机的具体情况和工作负载。

[更新]

OP似乎给人以印象

  • 哈希运算很慢
  • 快速哈希产生冲突
  • 使用散列总是需要读取完整的文件内容,因此,对于第一个字节不同的文件来说,这是多余的。

我添加了此部分来应对这些论点:

  • 在现代CPU上,强哈希(sha1)大约需要5 cycles per byte to compute,或每字节大约15ns。旋转的硬盘或固态硬盘的磁盘延迟分别约为75k ns和5M ns。您可以在开始从SSD读取数据时散列1k数据。更快的非加密哈希meowhash可以每个周期以1字节哈希。主内存等待时间约为120 ns-满足单个访问非缓存内存请求所需的时间很容易就有400个周期。
  • 2018年,SHA-1中唯一已知的冲突来自shattered项目,该项目需要大量资源来进行计算。其他强大的哈希算法并没有更慢,更强大(SHA-3)。
  • 您总是可以哈希文件的一部分而不是全部;并存储部分哈希值,直到遇到冲突为止,这时您将计算出越来越大的哈希值,直到对于真正的重复项,您将对整个对象进行哈希处理为止。这使您可以更快地建立索引。

我的意思不是说哈希是万事大吉。就是说,对于此应用程序来说,它非常有用,而不是真正的瓶颈:真正的瓶颈在于实际上遍历和读取文件系统的各个部分,这比任何哈希或比较慢得多,而且要慢得多内容。

答案 1 :(得分:2)

您所缺少的最重要的事情是,从真实的旋转磁盘读取两个或多个大文件时,逐个字节地比较它们会引起大量查找,这比单独散列每个哈希文件并比较它们要慢得多。散列。

当然,只有在文件实际相等或接近时才为true,否则比较可能会提前终止。您所谓的“常用算法”假设大小相等的文件可能匹配。通常,大型文件通常是

但是...

当所有相同大小的文件都足够小以适合内存时,那么读取它们并比较它们而无需使用密码散列的确可以更快。 (不过,有效的比较会涉及更简单的哈希)。

类似地,当特定长度的文件数足够小,并且您有足够的内存来以足够大的块比较它们时,那​​么再次进行直接比较可能会更快,因为查找代价会很小与散列成本相比。

当磁盘实际上不包含很多重复项时(例如,因为您要定期清理它们),但是确实有很多相同大小的文件(这要多得多)可能是某些媒体类型),那么再次读取大块数据并比较这些块而不进行散列处理确实确实要快得多,因为比较通常会提前终止。

另外,当您使用SSD而不是旋转盘片时,同样,读取和比较相同大小的所有文件(只要您读取适当大小的块)通常会更快,因为不会造成任何损失寻找。

因此,实际上在很多情况下您都正确地认为“常规”算法的运行速度不如预期的快。 现代重复数据删除工具可能应该检测到这些情况并切换策略。

答案 2 :(得分:2)

如果相同大小的所有文件组都可以放入物理内存,或者如果您具有非常快的SSD,则逐字节比较可能会更快。视文件的数量和性质,使用的哈希函数,缓存位置和实现细节而定,它的速度可能仍然较慢。

散列方法是一种适用于所有情况的模数非常简单的算法(对极为罕见的碰撞情况进行模运算)。它可以正常扩展到具有少量可用物理内存的系统。在某些特定情况下,它可能略小于最佳值,但应始终处于最佳状态。

要考虑的一些细节:

1)您是否测量并发现文件组内的比较是操作的昂贵部分?对于2TB硬盘驱动器而言,整个文件系统可能需要花费很长时间。实际执行了多少次哈希操作?文件组等有多大?

2)如其他地方所述,快速散列不一定必须查看整个文件。如果您有一些大小相同且不会重复的较大文件,则对文件的一小部分进行哈希处理将非常有效。在重复百分比很高的情况下,它实际上会使速度变慢,因此应根据文件知识切换启发式方法。

3)使用128位散列可能足以确定身份。在您的余生中,您可以每秒哈希一百万个随机对象,并且比看到碰撞更容易赢得彩票。这不是完美的方法,但务实的是,与工具中的哈希冲突相比,您在一生中由于磁盘故障而丢失数据的可能性要大得多。

4)特别是对于HDD(磁盘),顺序访问要比随机访问快得多。这意味着像对n个文件进行哈希处理这样的顺序操作要比逐个比较这些文件快得多(当它们不完全适合物理内存时会发生这种情况)。