Java:有效地计算大文件的SHA-256哈希值

ste*_*ita 14 java optimization hash performance sha256

我需要计算一个大文件(或其中一部分)的SHA-256哈希值.我的实现工作正常,但它比C++的CryptoPP计算慢得多(25分钟与10分钟~30GB文件).我需要的是在C++和Java中类似的执行时间,因此哈希几乎可以在几乎同时准备好.我也尝试了Bouncy Castle实现,但它给了我相同的结果.这是我如何计算哈希:

int buff = 16384;
try {
    RandomAccessFile file = new RandomAccessFile("T:\\someLargeFile.m2v", "r");

    long startTime = System.nanoTime();
    MessageDigest hashSum = MessageDigest.getInstance("SHA-256");

    byte[] buffer = new byte[buff];
    byte[] partialHash = null;

    long read = 0;

    // calculate the hash of the hole file for the test
    long offset = file.length();
    int unitsize;
    while (read < offset) {
        unitsize = (int) (((offset - read) >= buff) ? buff : (offset - read));
        file.read(buffer, 0, unitsize);

        hashSum.update(buffer, 0, unitsize);

        read += unitsize;
    }

    file.close();
    partialHash = new byte[hashSum.getDigestLength()];
    partialHash = hashSum.digest();

    long endTime = System.nanoTime();

    System.out.println(endTime - startTime);

} catch (FileNotFoundException e) {
    e.printStackTrace();
}
Run Code Online (Sandbox Code Playgroud)

jar*_*bjo 34

我的解释可能无法解决您的问题,因为它很大程度上取决于您的实际运行时环境,但是当我在系统上运行代码时,吞吐量受磁盘I/O的限制,而不是哈希计算.转换到NIO并没有解决问题,但这只是因为你正在以非常小的片段(16kB)读取文件.将我的系统上的缓冲区大小(buff)增加到1MB而不是16kB,使吞吐量增加一倍,但是大于50MB/s,我仍然受到磁盘速度的限制,无法完全加载单个CPU核心.

顺便说一句:你可以通过在FileInputStream周围包装DigestInputStream,读取文件并从DigestInputStream获取计算的哈希,而不是像在代码中那样手动将数据从RandomAccessFile拖拽到MessageDigest,从而大大简化你的实现.


我使用较旧的Java版本进行了一些性能测试,这里似乎存在Java 5和Java 6之间的相关差异.我不确定SHA实现是否已经优化,或者VM是否正在以更快的速度执行代码.我使用不同的Java版本(1MB缓冲区)获得的吞吐量是:

  • Sun JDK 1.5.0_15(客户端):28MB/s,受CPU限制
  • Sun JDK 1.5.0_15(服务器):45MB/s,受CPU限制
  • Sun JDK 1.6.0_16(客户端):42MB/s,受CPU限制
  • Sun JDK 1.6.0_16(服务器):52MB/s,受磁盘I/O限制(85-90%CPU负载)

我对加密器部件在CryptoPP SHA实现中的影响有点好奇,因为基准测试结果表明SHA-256算法在Opteron上只需要15.8个CPU周期/字节.遗憾的是,我无法在cygwin上使用gcc构建CryptoPP(构建成功,但生成的exe立即失败),但在CryptoPP中使用和不使用汇编程序支持并使用VS SHA构建VS2005(默认发行版配置)的性能基准测试在内存缓冲区上实现,省略任何磁盘I/O,我在2.5GHz Phenom上得到以下结果:

  • Sun JDK1.6.0_13(服务器):26.2个周期/字节
  • CryptoPP(仅限C++):21.8个周期/字节
  • CryptoPP(汇编程序):13.3个周期/字节

两个基准测试计算4GB空字节数组的SHA哈希值,以1MB的块为单位进行迭代,并传递给MessageDigest#update(Java)或CryptoPP的SHA256.Update函数(C++).

我能够在运行Linux的虚拟机中使用gcc 4.4.1(-O3)构建和标记CryptoPP,并且只获得了appr.与VS exe的结果相比,吞吐量的一半.我不确定虚拟机有多大的差异以及VS通常会产生比gcc更好的代码导致多少差异,但我现在无法从gcc获得更精确的结果.