質問

大きなファイル (またはその一部) の SHA-256 ハッシュを計算する必要があります。私の実装は正常に動作しますが、C++ の CryptoPP 計算よりもはるかに時間がかかります (25 分)。対約 30 GB のファイルの場合は 10 分)。必要なのは、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();
}
役に立ちましたか?

解決

実際のランタイム環境に大きく依存するため、私の説明では問題は解決しないかもしれませんが、私のシステムでコードを実行すると、スループットはハッシュ計算ではなくディスク I/O によって制限されます。この問題は NIO に切り替えることで解決されるわけではなく、単にファイルを非常に小さな断片 (16kB) で読み取っていることが原因で発生します。システムのバッファ サイズ (buff) を 16kB ではなく 1MB に増やすと、スループットは 2 倍以上になりますが、50MB/s を超えると、依然としてディスク速度の制限があり、単一の CPU コアを完全にロードすることができません。

ところで:コードのように RandomAccessFile から MessageDigest にデータを手動でシャッフルする代わりに、FileInputStream の周囲に DigestInputStream をラップし、ファイルを読み取って、DigestInputStream から計算されたハッシュを取得することで、実装を大幅に簡素化できます。


古い Java バージョンでいくつかのパフォーマンス テストを行ったところ、Java 5 と Java 6 の間には関連する違いがあるようです。ただし、SHA 実装が最​​適化されているのか、それとも VM がコードをより高速に実行しているのかはわかりません。さまざまな Java バージョン (1MB バッファー) で得られるスループットは次のとおりです。

  • Sun JDK 1.5.0_15 (クライアント):28MB/秒、CPU によって制限される
  • Sun JDK 1.5.0_15 (サーバー):45MB/秒、CPU によって制限される
  • Sun JDK 1.6.0_16 (クライアント):42MB/秒、CPU によって制限される
  • Sun JDK 1.6.0_16 (サーバー):52MB/秒、ディスク I/O によって制限される (85 ~ 90% の CPU 負荷)

CryptoPP SHA 実装におけるアセンブラ部分の影響については、少し興味がありました。 ベンチマーク結果 SHA-256 アルゴリズムは、Opteron 上で 15.8 CPU サイクル/バイトのみを必要とすることを示しています。残念ながら、cygwin 上の gcc を使用して CryptoPP をビルドすることはできませんでした (ビルドは成功しましたが、生成された exe はすぐに失敗しました)。しかし、CryptoPP でのアセンブラサポートの有無にかかわらず、VS2005 (デフォルトのリリース構成) でパフォーマンスベンチマークを構築し、Java SHA と比較しました。ディスク I/O を除外してメモリ内バッファに実装すると、2.5 GHz Phenom で次の結果が得られます。

  • Sun JDK1.6.0_13 (サーバー):26.2サイクル/バイト
  • CryptoPP (C++ のみ):21.8サイクル/バイト
  • CryptoPP (アセンブラー):13.3サイクル/バイト

どちらのベンチマークも、4 GB の空のバイト配列の SHA ハッシュを計算し、1 MB のチャンクで反復処理し、MessageDigest#update (Java) または CryptoPP の SHA256.Update 関数 (C++) に渡します。

Linux を実行している仮想マシンで gcc 4.4.1 (-O3) を使用して CryptoPP をビルドしてベンチマークすることができ、結果はおよそ 1.5 のみでした。VS exe からの結果と比較してスループットが半分になります。違いのどれくらいが仮想マシンに寄与しているのか、VS が通常 gcc よりも優れたコードを生成することがどれだけ原因であるのかはわかりませんが、現時点では gcc からより正確な結果を得る方法はありません。

他のヒント

おそらく今日最初にすることは、最も多くの時間を費やしている場所を調べることですか?プロファイラーを通してそれを実行して、最も多くの時間が費やされている場所を確認できますか。

考えられる改善点:

  1. NIO を使用して、次のファイルを読み取ります。 可能な限り最速の方法
  2. 別のスレッドでハッシュを更新します。これは実際にはかなり難しく、スレッド間で安全に公開する必要があるため、気の弱い人には向きません。ただし、プロファイリングでハッシュ アルゴリズムにかなりの時間が費やされていることが示された場合は、ディスクを有効に利用できる可能性があります。

私はあなたの時間が実際にその部分に過ごし、集中しているJProfilerをか調べるためにNetbeansの(無料)に統合され1、のようなプロファイラを使用することをお勧めます。

ただ、野生の推測 - それが役立つかどうかわからない - しかし、あなたはサーバーのVMを試してみましたか? java -serverでアプリを起動してみて、それがあなたを助けている場合参照してください。サーバーVMは、VMがデフォルトのクライアントよりもネイティブにJavaコードをコンパイルし、より積極的である。

これは、Javaが同じC ++コードに比べて約10倍遅く実行されたことにするために使用しました。今日では、より遅い2倍に近いです。私は何をあなたがに実行すると、Javaののちょうど基本的な部分だと思います。 JVMが新しいJIT技術が発見され、特にとして、より速くなりますが、あなたがCを実行する苦労を持っています。

あなたは、代替のJVMおよび/またはコンパイラを試してみましたか?私は JRocket にして、より良いパフォーマンスを得るために使用されるが、あまり安定。 javacの上でのJikes を使用するための同上。

あなたは明らかに高速で働いC ++の実装を持っているので、あなたは JNIするブリッジと実際のC ++の実装を使用するか、または多分あなたはそれが大きなものだ、特に以来、車輪の再発明ないようにしようと、このような<のhref =「http://www.bouncycastle.org/java.html」のrelとしてあらかじめ作られたライブラリを使用することができますあなたのプログラムのすべての暗号化ニーズを解決するためになされたもの=「nofollowをnoreferrer」>はBouncyCastle を。

私は、パフォーマンスの違いが唯一のプラットフォームに関連するかもしれないと思います。バッファサイズを変更してみて、どんな改善があるかどうかを確認。そうでない場合、私は JNI(Javaネイティブ・インターフェース)をして行くだろう。ただ、JavaからC ++実装を呼び出します。

あなたはいつも非常に遅く、性能面となっているのRandomAccessFileを使用しているため、

あなたのコードはとても遅いです主な理由です。あなたは、ディスクI / OのためのOSレベルのキャッシュのすべての電源から利益を得ることができるように、私は「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;
}
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top