我需要为重复数据删除目的计算一些非常大的文件MD5(> 1TB)。例如,我有一个10GiB文件,想要同时计算整个文件和10个连续1GiB块中每个文件的MD5。
我不能使用表格:
Digest::MD5.hexdigest(IO.read("file_10GiB.txt"))
因为Ruby在计算MD5之前首先在内存中读取整个文件,所以我很快耗尽了内存。
所以我可以使用以下代码在1MiB块中读取它。
require "digest"
fd = File.open("file_10GiB.txt")
digest_1GiB = Digest::MD5.new
digest_10GiB = Digest::MD5.new
10.times do
1024.times do
data_1MiB = fd.read(2**20) # Each MiB is read only once so
digest_1GiB << data_1MiB # there is no duplicate IO operations.
digest_10GiB << data_1MiB # It is then sent to both digests.
end
puts digest_1GiB.hexdigest
digest_1GiB.reset
end
puts digest_10GiB.hexdigest
大约需要40秒。使用openssl
代替digest
,结果相似。
如果我发表评论digest_1GiB << data_1MiB
或digest_10GiB << data_1MiB
,毫不奇怪,它的速度提高了两倍(20秒),bash命令md5sum file_10GiB.txt
需要大约20秒,所以这是一致的。
显然,两个MD5都是在同一个线程和核心中计算的,所以我想我可以使用一些多线程。我使用没有实际多线程但没有使用子进程的Ruby MRI 2.2.1我可以同时计算多个内核上的MD5:
fd = File.open("file_10GiB.txt")
IO.popen("md5sum", "r+") do |md5sum_10GiB|
10.times do
IO.popen("md5sum", "r+") do |md5sum_1GiB|
1024.times do
data_1MiB = fd.read(2**20) # Each MiB is read only once so
md5sum_1GiB << data_1MiB # there is no duplicate IO operations.
md5sum_10GiB << data_1MiB # It is then sent to both digests.
end
md5sum_1GiB.close_write
puts md5sum_1GiB.gets
end
end
md5sum_10GiB.close_write
puts md5sum_10GiB.gets
end
但这需要120秒,慢三倍。为什么会这样?
奇怪的是,如果我发表评论md5sum_1GiB << data_1MiB
或md5sum_10GiB << data_1MiB
,它不会像预期的那样花费60秒,而是40,这仍然是理论速度的一半。
使用Open3::popen2
代替IO::popen
的结果相似。 openssl md5
代替md5sum
。
我已经确认这些代码片段与大得多的文件的速度差异,以确保它不是无关紧要的测量误差,并且比例保持不变。
我有非常快的IO存储,大约2.5GiB / s顺序读取,所以我不认为这可能会造成任何限制。