3

所以我一直在寻找一种更快的方法来计算 MD5 校验和并运行Fast MD5 库——但是当我在我的机器上使用 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

根据评论,我一遍又一遍地运行基准测试并得到最奇怪的结果。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

4

3 回答 3

15

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

我认为当您再次运行代码时,您的 Java 7 执行时间大约会加倍,因为如果您只是将发布的代码放入 for 循环中,您会忘记total在第 2、3、4、...运行之前重置回 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 编译器之后执行的优化。

例如,nioByteBuffers专门设计用于通过使用诸如 DMA 之类的本机事物来更快地访问内存。因此,MD5 的 Java 7 实现使用 aByteBuffer作为输入而不是 a的事实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();花费的时间。这个数字应该相当小(可能小于 1ms)。

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

于 2013-02-10T06:19:15.170 回答
6

我写了自己的基准。我的答案:

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 或更大)的赢家。

甘道夫的问题:

  • 您确定您正在设置您的 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

于 2013-02-12T20:07:27.520 回答
0

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

  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) );
}
于 2013-02-07T03:28:22.110 回答