【问题标题】:Fast MD5 Library is not faster than Java 7 MD5?Fast MD5 Library 不比 Java 7 MD5 快吗?
【发布时间】:2013-01-31 23:29:51
【问题描述】:

所以我一直在寻找一种更快的方法来计算 MD5 校验和并遇到了 Fast MD5 library - 但是当我在我的机器上使用 Java 7 对其进行基准测试时,它比 Java 版本慢。

要么我在做一些愚蠢的事情(很可能),要么 Java 7 实现了更好的算法(也很可能)。这是我超级简单的“基准”——也许我今天没有喝足够的咖啡……

    MD5 digest = new MD5();
    System.out.println(MD5.initNativeLibrary(true));
    byte[] buff = IOUtils.readFully(new FileInputStream(new File("blahblah.bin")), 64000000, true);
    ByteBuffer buffer = ByteBuffer.wrap(buff);
    for (int j = 0; j < 100; j++) {
        start = System.currentTimeMillis();
        String md5Base64 = Utilities.getDigestBase64(buffer);
        end = System.currentTimeMillis();
        total = total + (end-start);
    }
    System.out.println("Took " + ((total)/100.00) + " ms. for " + buff.length+" bytes");
    total = 0;
    for (int i = 0; i < 100; i++) {
        start = System.currentTimeMillis();
        digest.Init();
        digest.Update(buff);
        digest.Final();
        end = System.currentTimeMillis();
        total = total + (end-start);
    }
    System.out.println("Took " + ((total)/100.00) + " ms. for " + buff.length+" bytes");

我得到:

Took 247.99 ms. for 64000000 bytes
Took 295.16 ms. for 64000000 bytes

根据评论,我一遍又一遍地运行 benchamrk,得到了最奇怪的结果。 FastMD5 计算保持不变,但 Java 7 版本变慢了。 ????

Took 246.54 ms. for 64000000 bytes
Took 294.69 ms. for 64000000 bytes
************************************
Took 540.55 ms. for 64000000 bytes
Took 292.69 ms. for 64000000 bytes
************************************
Took 537.07 ms. for 64000000 bytes
Took 292.12 ms. for 64000000 bytes

【问题讨论】:

  • 这里看不到问题。
  • @SecurityMatt - 因为这就是我所需要的。
  • @Gandalf VM/JIT 需要时间来预热(取决于实现和设置) - 我会将 both 测试作为 pair循环> 多次(比如 100 次,因此每个 MD5 哈希执行 100x100 或更多),然后查看是否有任何变化。此外,total 是 in 毫秒,所以 total/100.00 不再是毫秒 ..
  • 你能发个sscce吗?
  • @assylias 除非他的代码中存在明显的错误(请参阅我的回答),否则我相信 Gandalf 的结果非常合理。我认为判断基准的最佳方法是(a)尝试预测其结果,然后查看是否得到与此预测相当匹配的数字,以及(b)改变基准的参数(此处为数据大小)并检查对结果进行正确的更改(此处:带偏移的线性)。简单地使用声称做正确的事情的库并不会阻止优化,例如,也会弄乱你的结果。

标签: java performance md5 benchmarking


【解决方案1】:

我编写了自己的基准。我的回答:

It Depends!

这是我的结果(在 3.4-trunk-amd64 linux 和 Java 1.7.0_05 上运行):

1.) 对于少量数据,Java 胜出。

TINY DATA new byte[12]      SMALL DATA new byte[123]

Java builtin MD5...         Java builtin MD5...
encode 55 MB/s              encode 217 MB/s
encode 55 MB/s              encode 215 MB/s

Java Fast-MD5...            Java Fast-MD5...
encode 31 MB/s              encode 150 MB/s
encode 32 MB/s              encode 159 MB/s

Native Fast-MD5...          Native Fast-MD5...
encode 22 MB/s              encode 133 MB/s
encode 22 MB/s              encode 133 MB/s

2.) 从 1KB 及以后的数据,Native Fast-MD5 总是胜出:

MEDIUM DATA new byte[1234]  LARGE DATA new byte[12345]

Java builtin MD5...         Java builtin MD5...
encode 351 MB/s             encode 366 MB/s
encode 351 MB/s             encode 369 MB/s

Java Fast-MD5...            Java Fast-MD5...
encode 300 MB/s             encode 325 MB/s
encode 298 MB/s             encode 322 MB/s

Native Fast-MD5...          Native Fast-MD5...
encode 434 MB/s             encode 582 MB/s
encode 450 MB/s             encode 574 MB/s

3.) 速度在 12KB 后似乎趋于稳定。 123KB 没有真正的变化:

X-LARGE DATA new byte[123456]

Java builtin MD5...
encode 367 MB/s
encode 370 MB/s

Java Fast-MD5...
encode 325 MB/s
encode 324 MB/s

Native Fast-MD5...
encode 571 MB/s
encode 599 MB/s

结论:

  • 在我的设置中,Java 的内置 MD5 总是优于 Fast-MD5 的后备(非本机)实现。

  • 随着数据块变大,所有实现都加快了速度。

  • Fast-MD5 的本机实现在数据量更大(1KB 或更大)的情况下胜出。

Gandalf的问题:

  • 您确定要设置 Fast-MD5 安装以正确使用本机代码(例如,Fast-MD5 能够找到 MD5.so 或 MD5.dll)吗?

我真的不可能将基准放在一起作为“sscce”——它是 150 行!你可以在这里下载它,而不是:

http://juliusdavies.ca/base64bench/

像这样运行它(在用 ant 构建之后):

java ca.juliusdavies.base64bench.MD5BenchByte2Byte MD5.so

这里是基准源代码的直接链接:

http://juliusdavies.ca/base64bench/exploded/base64bench/src/java/ca/juliusdavies/base64bench/MD5BenchByte2Byte.java.html

【讨论】:

  • 我得到的结果非常相似 Native Fast-MD5 即使对于 100MB 数据也更快。
【解决方案2】:

让我们先回答你问题的简单部分:

我认为当您再次运行代码时,您的 Java 7 执行时间大约会加倍,因为如果您只是将发布的代码放入 for 循环中,您会忘记在第 2、3、4 次之前将 total 重置为 0 ,... Java 7 测试的运行(对于第一个测试,它可能从变量初始化设置为 0)。

因此,只需将未设置回 0 的偏移量减去即可修复表格:

Took 246.54 ms. for 64000000 bytes
Took 294.69 ms. for 64000000 bytes              <---.
************************************                |
Took 245.86 ms. for 64000000 bytes   (subtracting 294.69)
Took 292.69 ms. for 64000000 bytes              <---.
************************************                |
Took 244.38 ms. for 64000000 bytes   (subtracting 292.69)
Took 292.12 ms. for 64000000 bytes

现在,事情似乎非常一致,甚至显示了其他回复之一中提到的“JVM 预热”,并且只产生了大约 1% 的差异。

现在,为什么 Java 7 的性能优于 FastMD5?

他们可能使用了更好的算法,该算法更适合 Java 编译器之后执行的优化。

例如,nio ByteBuffers 专门设计用于通过使用诸如 DMA 之类的原生事物来更快地访问内存。因此,MD5 的 Java 7 实现使用ByteBuffer 作为输入而不是byte[],这一事实让我认为他们实际上是在利用这些功能(否则他们可能也只是采用了byte[] .)

再说什么,我们可能需要知道您的Utilities 对象究竟做了什么,例如,然后比较 FastMD5 的源代码和 Java 实现。

但我想说:您的结果(给定总 = 0 修复)对我来说非常有意义,您可能会喜欢这样一个事实,即您可以减少对外部库的依赖! ;)

顺便说一句:您看到的性能差异仅对应于 3.5GHz CPU 上每个处理的数据字节大约 2-3 个 CPU 时钟周期(每个字节总共大约 15 个时钟周期)。因此,鉴于差异非常小,它很可能取决于所使用的确切平台和 JVM,两者中的一个最终会更快。

加法

您的基准测试数据表明,您可以使用两种 MD5 实现处理大约 220-260MB/s,如果您查看 Google 搜索显示的其他声称的规格(例如,“结果实现”下的 http://www.zorinaq.com/papers/md5-amd64.html),这听起来很合理。因此,与您收到的所有其他回复相反,我确实觉得我会相信您的数字。

如果您想更加确定,请改变 byte[] 的大小并查看由此产生的处理时间变化。如果一切正常,你会看到一个线性关系,你可以使用这个函数:

total/100.0 = m * buff.length + b           (your usual y = mx + b)

这里,m 是每个字节的处理时间,应该在 1 / 250MB/s = 4ns/byte 左右,b 是函数用于初始化局部变量等的设置时间,以及System.currentTimeMillis(); 花费的时间。这个数字应该相当小(可能小于 1 毫秒)。

然后,要确定这两种算法中哪一种更适合您,您需要比较mb。如果您通常处理小型数据数组,b 在确定哪种算法更好时可能比m 更重要,而对于大型数据集,m 较小的算法更好

【讨论】:

  • 该死的好球 - 菜鸟错误没有将总数设置回 0。
  • @Gandalf 发生在我们最好的人身上! ;)
【解决方案3】:

分析时,以下规则很重要:

  1. 您关心的是摊销案例,而不是第一次运行。因此,在循环中重复运行测试并等待它解决。

  2. 您需要小心配置文件本身。在您的情况下,System.currentTimeMillis 的前几次运行所用的时间比后来的运行时间长得多,这可能会完全扭曲您的性能指标。

  3. 始终测量大量迭代,切勿单独测量单个迭代。

  4. 迭代次数需要很大才能有任何意义,并且您需要多次运行测试才能获得公正的评估。

尝试运行类似于以下的内容:

MD5 digest = new MD5();
System.out.println(MD5.initNativeLibrary(true));
byte[] buff = IOUtils.readFully(new FileInputStream(new File("blahblah.bin")), 64000000, true);
ByteBuffer buffer = ByteBuffer.wrap(buff);

int iterations = 10000;

while(true)
{
   //
   // 1. Run the first test:
   //
   start = System.currentTimeMillis();
   for (int j = 0; j < iterations; j++) {
       String md5Base64 = Utilities.getDigestBase64(buffer);
   }
   end = System.currentTimeMillis();
   System.out.println("(1) " + (start - end) );

   //
   // 2. Run the second test:
   //
   start = System.currentTimeMillis();
   for (int i = 0; i < iterations; i++) {
      digest.Init();
      digest.Update(buff);
      digest.Final();
   }
   end = System.currentTimeMillis();

   System.out.println("(2) " + (start - end) );
}

【讨论】:

  • 我每个跑100次,1000次,没区别。
  • 您是否尝试过修复我在回答中指出的任何其他问题(例如,您是否尝试过类似于我发布的代码的内容)? 1000次也不是很多。尝试运行 100,000 次。
  • 在同一方法中运行 2 个测试是没有意义的。只有其中一个会得到优化。而且 10,000 次迭代太少了,它甚至不会让 JIT 编译任何东西......
  • @assylias:它们都将被优化。而且我没有运行 10,000 次测试。每次循环 while(true) 循环时,我都会运行 10,000 次测试。
  • @assylias:我没有改变 ops 原始循环的内部结构。请注意,编译器也可以将第二个循环优化为空操作,因为它可以内联所有代码并删除未检查的分配。基准本质上是有偏差的,因为它们在不自然的位置测量代码。我的代码 sn-p 与其说是“这是运行的最佳基准”,不如说是对操作自己的基准的批评集合。
猜你喜欢
  • 2013-03-08
  • 2010-11-05
  • 1970-01-01
  • 1970-01-01
  • 2018-05-16
  • 1970-01-01
  • 1970-01-01
  • 2012-06-17
  • 1970-01-01
相关资源
最近更新 更多