Java: Эффективно вычислить хэш большого файла SHA-256

Убедитесь, что у вас есть правильный адрес электронной почты перед отправкой сообщений электронной почты. Если кто-то задает неправильный адрес электронной почты при регистрации, побейте их по голове как можно скорее.

Всегда включайте четкую информацию о том, как отказаться от подписки, в КАЖДОЙ электронной почте.

Это не позволит пользователям помечать ваши письма как спам, потому что «отмена подписки» слишком сложна.

Не нужно подписывать пользователя для отказа от подписки, это должен быть уникальный URL-адрес для отмены подписки на 1 клик. ]
13
задан Nat 18 January 2017 в 23:15
поделиться

7 ответов

Мое объяснение может не решить вашу проблему, поскольку оно во многом зависит от вашей реальной среды выполнения, но когда я запускаю ваш код в своей системе, пропускная способность ограничивается дисковым вводом-выводом, а не расчет хеша. Проблема не решается переключением на NIO, а просто вызвана тем, что вы читаете файл очень маленькими частями (16 КБ). Увеличение размера буфера (бафф) в моей системе до 1 МБ вместо 16 КБ увеличивает пропускную способность более чем вдвое, но при> 50 МБ / с я все еще ограничен скоростью диска и не могу полностью загрузить одно ядро ​​ЦП.

BTW : Вы можете значительно упростить свою реализацию, обернув DigestInputStream вокруг FileInputStream, прочтите файл и получите рассчитанный хэш из DigestInputStream вместо того, чтобы вручную перетасовывать данные из RandomAccessFile в MessageDigest, как в вашем коде.


Я провел несколько тестов производительности со старыми версиями Java, и, похоже, есть существенная разница между Java 5 и Java 6 здесь. Однако я не уверен, оптимизирована ли реализация SHA или виртуальная машина выполняет код намного быстрее. Пропускная способность, которую я получаю с разными версиями 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% ЦП load)

Мне было немного любопытно, как влияет ассемблер на реализацию CryptoPP SHA, поскольку результаты тестов показывают, что алгоритм SHA-256 требует на Opteron всего 15,8 циклов ЦП на байт. К сожалению, мне не удалось собрать CryptoPP с помощью gcc на cygwin (сборка прошла успешно, но сгенерированный exe сразу же не удалось), но построил тест производительности с VS2005 (конфигурация выпуска по умолчанию) с поддержкой ассемблера в CryptoPP и без нее и по сравнению с Java SHA реализация в буфере в памяти, без учета дискового ввода-вывода, я получаю следующие результаты на Phenom 2,5 ГГц:

  • Sun JDK1.6.0_13 (сервер): 26,2 цикла / байт
  • CryptoPP (только C ++ ): 21,8 цикла / байт
  • CryptoPP (ассемблер): 13,3 цикла / байт

Оба теста вычисляют хэш SHA для массива пустых байтов размером 4 ГБ, выполняя итерацию по нему кусками по 1 МБ, которые передаются в MessageDigest # update ( Java) или SHA256 от CryptoPP. Функция обновления (C ++).

Я смог собрать и протестировать CryptoPP с gcc 4.4.1 (-O3) на виртуальной машине под управлением Linux и получил только ок. вдвое меньше пропускной способности по сравнению с результатами VS exe. Я не уверен, какая разница вносится в виртуальную машину, а какая из-за того, что VS обычно производит лучший код, чем gcc, но у меня нет возможности получить более точные результаты прямо сейчас.

34
ответ дан 1 December 2019 в 06:43
поделиться

Возможно, первое, что нужно сделать сегодня, - это выяснить, где вы проводите больше всего времени? Можете ли вы запустить его через профилировщик и посмотреть, на что тратится больше всего времени.

Возможные улучшения:

  1. Используйте NIO для чтения файла максимально быстрым способом
  2. Обновите хэш в отдельном потоке . На самом деле это довольно сложно сделать, и это не для слабонервных, поскольку требует безопасной публикации между потоками. Но если ваше профилирование показывает, что значительное количество времени тратится на хэш-алгоритм, возможно, лучше использовать диск.
4
ответ дан 1 December 2019 в 06:43
поделиться

Я предлагаю вы используете профилировщик, такой как JProfiler, или тот, который интегрирован в Netbeans (бесплатно), чтобы узнать, где на самом деле тратится время, и сконцентрироваться на этой части.

Просто дикая догадка - не уверен, поможет ли это - но пробовали ли вы Серверная ВМ? Попробуйте запустить приложение с java -сервер и посмотрите, поможет ли это вам. Серверная виртуальная машина более агрессивно компилирует Java-код в собственный, чем клиентская виртуальная машина по умолчанию.

где на самом деле тратится время, и сконцентрируйтесь на этой части.

Просто дикая догадка - не уверен, поможет ли это - но пробовали ли вы виртуальную машину сервера? Попробуйте запустить приложение с java -сервер и посмотрите, поможет ли это вам. Серверная виртуальная машина более агрессивно компилирует Java-код в собственный, чем клиентская виртуальная машина по умолчанию.

где на самом деле тратится время, и сконцентрируйтесь на этой части.

Просто дикая догадка - не уверен, поможет ли это - но пробовали ли вы виртуальную машину сервера? Попробуйте запустить приложение с java -сервер и посмотрите, поможет ли это вам. Серверная виртуальная машина более агрессивно компилирует Java-код в собственный, чем клиентская виртуальная машина по умолчанию.

2
ответ дан 1 December 2019 в 06:43
поделиться

Раньше Java работала примерно в 10 раз медленнее, чем тот же код C ++. В настоящее время почти в 2 раза медленнее. Я думаю, что то, с чем вы столкнулись, - это просто фундаментальная часть Java. JVM станут быстрее, особенно по мере открытия новых JIT-технологий, но вам будет трудно выполнять C.

Пробовали ли вы альтернативные JVM и / или компиляторы? Раньше я получал лучшую производительность с JRocket , но с меньшей стабильностью. То же самое для использования jikes поверх javac.

1
ответ дан 1 December 2019 в 06:43
поделиться

Поскольку у вас явно есть рабочий Реализация C ++ является быстрой, вы можете построить мост JNI и использовать реальную реализацию C ++ или, может быть, вы могли бы попробовать не изобретать велосипед, тем более что он большой, и использовать готовую библиотеку, такую ​​как BouncyCastle , который был создан для решения всех криптографических потребностей вашей программы.

1
ответ дан 1 December 2019 в 06:43
поделиться

Я думаю, что эта разница в производительности может быть связана только с платформой. Попробуйте изменить размер буфера и посмотрите, есть ли улучшения. Если нет, я бы выбрал JNI (собственный интерфейс Java) . Просто вызовите реализацию C ++ из Java.

1
ответ дан 1 December 2019 в 06:43
поделиться

Основная причина, по которой ваш код работает так медленно, заключается в том, что вы используете RandomAccessFile, который всегда был достаточно медленным. Я предлагаю использовать "BufferedInputStream", чтобы вы могли извлечь выгоду из всей мощи кэширования на уровне операционной системы для disk-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;
}
0
ответ дан 1 December 2019 в 06:43
поделиться
Другие вопросы по тегам:

Похожие вопросы: