【问题标题】:fast md5sum on millions of strings in bash/ubuntu在 bash/ubuntu 中对数百万个字符串进行快速 md5sum
【发布时间】:2010-12-30 00:35:43
【问题描述】:

我需要 ubuntu 上的 bash 脚本中的 300 万个字符串的 MD5 总和。 300 万个字符串 -> 300 万个 MD5 哈希。简单的实现每个字符串大约需要 0.005 秒。那是4个多小时。有哪些更快的替代方案?有没有办法将字符串组泵入 md5sum?

#time md5sum running 100 times on short strings
#each iteration is ~0.494s/100 = 0.005s
time (for i in {0..99}; do md5sum <(echo $i); done) > /dev/null

real    0m0.494s
user    0m0.120s
sys     0m0.356s

一个好的解决方案将包括一个 bash/Perl 脚本,该脚本从标准输入获取字符串列表并输出其 MD5 哈希列表。

【问题讨论】:

  • 你要破解谁的密码? ;)
  • 没有什么比这更令人兴奋了,真的 :)

标签: ubuntu md5 md5sum


【解决方案1】:

在 C(或 Perl 或 Python)中,使用许多 md5 实现中的任何一个并不难——md5 的核心是一个从字符向量到字符向量的散列函数。

所以只需编写一个外部程序来读取您的 300 万个字符串,然后将它们一一提供给您选择的 md5 实现。这样一来,您就可以启动一个程序,而不是 300 万个,仅此一项就可以节省您的时间。

FWIW 在一个项目中我使用了 Christophe Devine 的 md5 实现(用 C 语言),还有 OpenSSL,我相信 CPAN 也会有一些用于 Perl。

编辑:好吧,忍不住了。我提到的 md5 实现是例如在这个small tarball 里面。获取文件md5.c 并将底部的(#ifdef'ed out)main() 替换为此

int main( int argc, char *argv[] ) {
    FILE *f;
    int j;
    md5_context ctx;
    unsigned char buf[1000];
    unsigned char md5sum[16];

    if( ! ( f = fopen( argv[1], "rb" ) ) ) {
        perror( "fopen" );
        return( 1 );
    }

    while( fscanf(f, "%s", buf) == 1 ) {
        md5_starts( &ctx );
        md5_update( &ctx, buf, (uint32) strlen((char*)buf) );
        md5_finish( &ctx, md5sum );

        for( j = 0; j < 16; j++ ) {
            printf( "%02x", md5sum[j] );
        }
        printf( " <- %s\n", buf );
    }
    return( 0 );
}

构建一个简单的独立程序,例如在

/tmp$ gcc -Wall -O3 -o simple_md5 simple_md5.c

然后你得到这个:

# first, generate 300,000 numbers in a file (using 'little r', an R variant)
/tmp$ r -e'for (i in 1:300000) cat(i,"\n")' > foo.txt

# illustrate the output
/tmp$ ./simple_md5 foo.txt | head
c4ca4238a0b923820dcc509a6f75849b <- 1
c81e728d9d4c2f636f067f89cc14862c <- 2
eccbc87e4b5ce2fe28308fd9f2a7baf3 <- 3
a87ff679a2f3e71d9181a67b7542122c <- 4
e4da3b7fbbce2345d7772b0674a318d5 <- 5
1679091c5a880faf6fb5e6087eb1b2dc <- 6
8f14e45fceea167a5a36dedd4bea2543 <- 7
c9f0f895fb98ab9159f51fd0297e236d <- 8
45c48cce2e2d7fbdea1afc51c7c6ad26 <- 9
d3d9446802a44259755d38e6d163e820 <- 10

# let the program rip over it, suppressing stdout
/tmp$ time (./simple_md5 foo.txt > /dev/null)

real    0m1.023s
user    0m1.008s
sys     0m0.012s
/tmp$

所以对于 300,000 个(短)字符串来说,这大约是一秒。

【讨论】:

  • 在我的机器上,这比 bash/md5sum 快大约 3 个数量级。这并不是那么简洁,但速度是不容争辩的。对于未来的读者,请注意 fscanf 会读取直到遇到空格;如果您需要处理整行,请考虑 fgets 或类似的,并考虑是否需要哈希中的终止换行符。在有人开始编写 GPU 加速的 MD5 之前,我将给出这个答案。速度提高了 1000 倍,我的瓶颈转移到了其他地方。感谢 Dirk、kread、John、gbacon 和其他人。
【解决方案2】:
#~/sw/md5$ time (for i in {0..99}; do md5sum <(echo $i); done) > /dev/null

real    0m0.220s
user    0m0.084s
sys 0m0.160s
#~/sw/md5$ time (python test.py `for i in {0..99}; do echo $i; done`) > /dev/null

real    0m0.041s
user    0m0.024s
sys 0m0.012s

对于这个小样本,python 代码的速度是五倍,对于较大的样本,由于缺少生成,差异会大得多。 1k 个样本是 0.033s 到 2.3s :) 脚本是:

#!/usr/bin/env python
import hashlib, sys

for arg in sys.argv[1:]:
  print hashlib.md5(arg).hexdigest()

【讨论】:

  • 对于较长的列表,它可能快 2 个数量级 - 谢谢!我没有意识到 python 解决方案可以如此简洁。哈希值不同。可能包含或不包含换行符。我会调查的。再次感谢!
  • 它很简洁,但它从命令行获取参数,不会针对 3,000,000 个字符串进行缩放。
  • 实际上,当修改 python 代码以从标准输入而不是命令行读取时,我的速度接近或超过(!)C 解决方案。将瓶颈转移到 IO 似乎足够快。虽然我已经给出了 C 解决方案的答案,但如果您将来阅读本文,您可能想先尝试使用 python 方法。要从标准输入读取,请将循环更改为:“for line in sys.stdin: print hashlib.md5(line).hexdigest()”
  • 在 Python 2.6 或更高版本中,使用多处理模块将其扩展到更多处理器也很容易。创建一个输入的 Queue 对象,一个用于结果的对象,产生一些进程(可能基于处理器的数量,或命令行开关等),并让所有子进程消耗输入,计算它们的哈希值并提供给结果通过结果队列返回给主节点。
  • 单行:python -c "exec(\"import hashlib, sys\nfor line in sys.stdin:\n\tprint hashlib.md5(line).hexdigest()\")"
【解决方案3】:
perl -MDigest::MD5=md5_hex -lpe '$_ = md5_hex $_'

【讨论】:

  • 感谢您的简洁回答;不过,它比问题中的 md5sum 循环长约 200%。
  • 您是每行输入派生一个 perl 进程还是在单个 perl 进程的标准输入上提供所有行?
  • 啊,我确实每行 fork 一个 perl 进程。当管道连接到单个 perl 进程时,我的速度提高了 3 倍。谢谢!
  • 我在我的哈希器上使用了这个例子,但我不知道它是如何工作的,大声笑你能解释一下选项和 lpe 选项吗?我需要这个用于漩涡和成熟 160 ;-)
  • @SebastianHeyn 请参阅perlrun documentation-lprint 的所有输出添加一个换行符。 -e 指定要评估的代码。 -p 将您的代码包装在while (&lt;&gt;) { ... } continue { print } 形式的循环中,您的代码将代替...。对于每一行输入(右侧的$_),代码计算其MD5哈希并将其存储到$_print的默认操作数。
【解决方案4】:

我现在没有机器可以测试它,但md5sum &lt;&lt;&lt; "$i"md5sum &lt;(echo $i) 快吗? &lt;&lt;&lt; 语法将避免为echo 派生子进程的开销,并将$i 直接传递给标准输入上的md5sum

【讨论】:

  • 很好——它在我的盒子上减少了大约三分之一。
  • 是的,也在我的盒子上。好收获!
【解决方案5】:

为了获得更好的性能,您可能需要使用不同的程序,或者创建一个调用公开可用的 md5 哈希 API 之一的 C 程序。

另一种选择是一次产生多个 md5 调用以利用多个内核。每个循环都可以产生 8 个调用,前 7 个使用 & 最后(表示异步)。如果您有 4-8 个内核可用,这可以将速度提高 8 倍。

【讨论】:

  • 我不确定 8 的因数——您还需要将输入传输到内核,这个问题可能比 cpu-bound 更受 i/o 限制,所以我认为您得到了次线性加速。但总的来说,是的,这是“令人尴尬的并行”,并且可以通过这种方式实现仍然不错的加速。
猜你喜欢
  • 2011-02-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-14
相关资源
最近更新 更多