Ruby:使用IO:popen子进程多线程处理多个MD5摘要

时间:2015-03-09 20:07:47

标签: ruby multithreading md5 popen

我需要为重复数据删除目的计算一些非常大的文件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_1MiBdigest_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_1MiBmd5sum_10GiB << data_1MiB,它不会像预期的那样花费60秒,而是40,这仍然是理论速度的一半。

使用Open3::popen2代替IO::popen的结果相似。 openssl md5代替md5sum

我已经确认这些代码片段与大得多的文件的速度差异,以确保它不是无关紧要的测量误差,并且比例保持不变。

我有非常快的IO存储,大约2.5GiB / s顺序读取,所以我不认为这可能会造成任何限制。

0 个答案:

没有答案