我需要计算一个大文件(或其中一部分)的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();
}
答案 0 :(得分:34)
我的解释可能无法解决您的问题,因为它很大程度上取决于您的实际运行时环境,但是当我在系统上运行代码时,吞吐量受磁盘I / O的限制,而不是哈希计算。切换到NIO没有解决问题,但这只是因为你正在以非常小的片段(16kB)读取文件。将我的系统上的缓冲区大小(buff)增加到1MB而不是16kB,使吞吐量增加一倍,但是在> 50MB / s时,我仍然受到磁盘速度的限制而无法完全加载单个CPU核心。
BTW:您可以通过在FileInputStream周围包装DigestInputStream,读取文件并从DigestInputStream获取计算的哈希,而不是像在代码中那样手动将数据从RandomAccessFile拖放到MessageDigest来简化您的实现。< / p>我使用较旧的Java版本进行了一些性能测试,这里似乎存在Java 5和Java 6之间的相关差异。我不确定SHA实现是否已经优化,或者VM是否正在以更快的速度执行代码。我使用不同的Java版本(1MB缓冲区)获得的吞吐量是:
我对加密器部件在CryptoPP SHA实现中的影响有点好奇,因为benchmarks results表明SHA-256算法在Opteron上只需要15.8个CPU周期/字节。遗憾的是,我无法在cygwin上使用gcc构建CryptoPP(构建成功,但生成的exe立即失败),但在CryptoPP中使用和不使用汇编程序支持并使用VS SHA构建VS2005(默认发行版配置)的性能基准测试在内存缓冲区上实现,省略任何磁盘I / O,我在2.5GHz Phenom上得到以下结果:
这两个基准测试计算4GB空字节数组的SHA哈希值,以1MB的块为单位进行迭代,传递给MessageDigest#update(Java)或CryptoPP的SHA256.Update函数(C ++)。
我能够在运行Linux的虚拟机中使用gcc 4.4.1(-O3)构建和标记CryptoPP,并且只获得了appr。与VS exe的结果相比,吞吐量的一半。我不确定虚拟机有多大的差异以及VS通常会产生比gcc更好的代码导致多少差异,但我现在无法从gcc获得更精确的结果。
答案 1 :(得分:4)
也许今天的第一件事就是找出你花费最多时间的地方?您可以通过分析器运行它,看看花费的时间最多。
可能的改进:
答案 2 :(得分:2)
我建议您使用像JProfiler这样的分析器或集成在Netbeans中的分析器(免费)来查找实际花费的时间并专注于该部分。
只是一个疯狂的猜测 - 不确定它是否会有所帮助 - 但你尝试过服务器虚拟机吗?尝试使用java -server
启动应用,看看是否对您有所帮助。与默认客户端VM相比,服务器VM更积极地将Java代码编译为本机。
答案 3 :(得分:1)
以前,Java比同一个C ++代码慢了大约10倍。如今速度接近2倍。我认为你遇到的只是Java的一个基本部分。 JVM会变得更快,特别是在发现新的JIT技术时,你将很难执行C。
您是否尝试过其他JVM和/或编译器?我曾经使用JRocket获得更好的性能,但稳定性较差。同样使用jikes而不是javac。
答案 4 :(得分:1)
由于你显然有一个很快的工作C ++实现,你可以构建一个JNI桥并使用实际的C ++实现,或者你可以尝试不重新发明轮子,特别是因为它是一个很大的并使用一个预制库,例如BouncyCastle,用于解决程序的所有加密需求。
答案 5 :(得分:1)
我认为这种性能差异可能只与平台有关。尝试更改缓冲区大小,看看是否有任何改进。如果没有,我会选择JNI (Java Native Interface)。只需从Java调用C ++实现。
答案 6 :(得分:0)
你的代码如此之慢的主要原因是因为你使用的RandomAccessFile在性能方面总是很慢。我建议使用“BufferedInputStream”,以便您可以从磁盘操作系统的操作系统级缓存的所有功能中受益。
代码应该类似于:
public static byte [] hash(MessageDigest digest, BufferedInputStream in, int bufferSize) throws IOException {
byte [] buffer = new byte[bufferSize];
int sizeRead = -1;
while ((sizeRead = in.read(buffer)) != -1) {
digest.update(buffer, 0, sizeRead);
}
in.close();
byte [] hash = null;
hash = new byte[digest.getDigestLength()];
hash = digest.digest();
return hash;
}