Java: высоко оценивать хэш SHA-256 большого файла
Мне нужно вычислить хэш SHA-256 большого файла (или его части). Моя реализация работает отлично, но она намного медленнее, чем C + + CryptoPP (25 мин. Против 10 минут для ~ 30 ГБ файла). Мне нужно аналогичное время выполнения на С++ и 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();
}
Ответы
Ответ 1
Мое объяснение может не решить вашу проблему, поскольку она сильно зависит от вашей реальной среды выполнения, но когда я запускаю свой код в своей системе, пропускная способность ограничена дисковым вводом-выводом, а не вычислением хэша. Проблема не решена путем переключения на NIO, но просто вызвана тем фактом, что вы читаете файл в очень маленьких частях (16 КБ). Увеличение размера буфера (buff) в моей системе до 1 Мбайт вместо 16 КБ больше, чем удваивает пропускную способность, но s > 50 Мбайт/с, я все еще ограничен скоростью диска и не способен полностью загружать одно ядро процессора.
BTW: вы можете значительно упростить реализацию, обернув DigestInputStream вокруг FileInputStream, прочитайте файл и получите вычисленный хэш из DigestInputStream, вместо того, чтобы вручную перетасовывать данные из RandomAccessFile в MessageDigest, как в вашем коде.
Я провел несколько тестов производительности со старыми версиями Java, и, похоже, здесь существует существенная разница между Java 5 и Java 6. Я не уверен, что если оптимизация SHA оптимизирована или VM быстрее выполняет код. Производительность, которую я получаю с различными версиями Java (1 МБ-буфер):
- Sun JDK 1.5.0_15 (клиент): 28 МБ/с, ограниченный процессором.
- Sun JDK 1.5.0_15 (сервер): 45 МБ/с, ограниченный процессором.
- Sun JDK 1.6.0_16 (клиент): 42 МБ/с, ограниченный процессором.
- Sun JDK 1.6.0_16 (сервер): 52 МБ/с, ограниченный дисковым вводом/выводом (85-90% загрузки процессора)
Мне было немного интересно узнать о влиянии части ассемблера на реализацию CryptoPP SHA, поскольку результаты тестов показывают, что SHA- 256 требуется только 15,8 циклов/байт процессора на Opteron. Я, к сожалению, не смог построить CryptoPP с gcc на cygwin (сборка выполнена успешно, но сгенерированный exe сработал сразу), но построил тест производительности с VS2005 (настройка выпуска по умолчанию) с поддержкой ассемблера и без поддержки ассемблера в CryptoPP и сравнением с Java SHA реализация в буфере в памяти, исключая любые дисковые операции ввода-вывода, я получаю следующие результаты на 2,5 ГГц Phenom:
- Sun JDK1.6.0_13 (сервер): 26,2 цикла/байт
- CryptoPP (только С++): 21.8 циклов/байт
- CryptoPP (ассемблер): 13,3 цикла/байт
Оба теста вычисляют SHA-хэш из 4-байтового пустого байтового массива, итерации по нему в кусках 1 МБ, которые передаются в MessageDigest # update (Java) или CryptoPP SHA256.Update function (С++).
Мне удалось построить и сравнить CryptoPP с gcc 4.4.1 (-O3) на виртуальной машине под управлением Linux и получить только appr. наполовину пропускную способность по сравнению с результатами VS exe. Я не уверен, какая разница в виртуальной машине и насколько она вызвана VS, обычно создавая лучший код, чем gcc, но у меня нет возможности получить более точные результаты от gcc прямо сейчас.
Ответ 2
Возможно, первое, что сегодня стоит, это выработка, когда вы проводите больше времени? Можете ли вы запустить его через профилировщик и посмотреть, сколько времени расходуется.
Возможные улучшения:
- Используйте NIO, чтобы прочитать файл в наиболее быстрый способ
- Обновите хэш в отдельном потоке. Это на самом деле довольно сложно сделать и не для слабонервных, поскольку это предполагает безопасную публикацию между потоками. Но если ваше профилирование показывает значительное количество времени, затрачиваемого на алгоритм хеширования, оно может лучше использовать диск.
Ответ 3
Я предлагаю вам использовать профилировщик вроде JProfiler или тот, который интегрирован в Netbeans (бесплатно), чтобы узнать, где время фактически потрачено и сосредоточиться на этой части.
Просто дикая догадка - не уверен, что это поможет - но попробовали ли вы Server VM? Попробуйте запустить приложение с помощью java -server
и посмотрите, поможет ли это вам. VM сервера более агрессивно компилирует Java-код к native, чем клиентская виртуальная машина по умолчанию.
Ответ 4
Раньше считалось, что Java работает примерно на 10 раз медленнее, чем тот же код на С++. В настоящее время ближе к 2x медленнее. Я думаю, что ваш опыт работы является лишь фундаментальной частью Java. JVM будут быстрее, особенно при обнаружении новых методов JIT, но вам будет сложно выполнить C.
Вы пробовали альтернативные JVM и/или компиляторы? Раньше я получал лучшую производительность с JRocket, но меньше стабильности. То же самое для использования jikes над javac.
Ответ 5
Поскольку у вас, по-видимому, есть работающая реализация С++, которая быстрая, вы можете построить мост JNI и использовать фактическую реализацию на С++ или, может быть, вы может попытаться не изобретать колесо, тем более, что он большой и использовать готовую библиотеку, такую как BouncyCastle, которая была сделана для решения всех криптографические потребности вашей программы.
Ответ 6
Я думаю, что это различие в производительности может быть связано только с платформой. Попробуйте изменить размер буфера и посмотреть, есть ли какие-либо улучшения. Если нет, я бы пошел с JNI (Java Native Interface). Просто вызовите реализацию С++ из Java.
Ответ 7
Причина MAIN, почему ваш код настолько медленный, заключается в том, что вы используете RandomAccessFile, который всегда был довольно медленным по производительности. Я предлагаю использовать "BufferedInputStream", чтобы вы могли использовать все возможности кэширования на уровне ОС для диска-i/o.
Код должен выглядеть примерно так:
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;
}