3

同じ機能の次の 2 つのバージョン (基本的には力ずくでパスワードを回復しようとする) では、同じパフォーマンスが得られません。

バージョン 1:

private static final char[] CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".toCharArray();
private static final int N_CHARS = CHARS.length;
private static final int MAX_LENGTH = 8;

private static char[] recoverPassword()
{
   char word[];
   int refi, i, indexes[];

   for (int length = 1; length <= MAX_LENGTH; length++)
   {
      refi = length - 1;
      word = new char[length];
      indexes = new int[length];
      indexes[length - 1] = 1;

      while(true)
      {
         i = length - 1;
         while ((++indexes[i]) == N_CHARS)
         {
            word[i] = CHARS[indexes[i] = 0];
            if (--i < 0)
               break;
         }

         if (i < 0)
            break;

         word[i] = CHARS[indexes[i]];

         if (isValid(word))
            return word;
      }
   }
   return null;
}

バージョン 2:

private static final char[] CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".toCharArray();
private static final int N_CHARS = CHARS.length;
private static final int MAX_LENGTH = 8;

private static char[] recoverPassword()
{
   char word[];
   int refi, i, indexes[];

   for (int length = 1; length <= MAX_LENGTH; length++)
   {
      refi = length - 1;
      word = new char[length];
      indexes = new int[length];
      indexes[length - 1] = 1;

      while(true)
      {
         i = refi;
         while ((++indexes[i]) == N_CHARS)
         {
            word[i] = CHARS[indexes[i] = 0];
            if (--i < 0)
               break;
         }

         if (i < 0)
            break;

         word[i] = CHARS[indexes[i]];

         if (isValid(word))
            return word;
      }
   }
   return null;
}

私はバージョン 2 が高速であることを期待しています (それが唯一の違いです)。

i = refi;

...バージョン 1 との比較:

i = length -1;

しかし、それは逆です: バージョン 1 は 3% 以上高速です! 誰かが理由を知っていますか?それは、コンパイラによって行われたいくつかの最適化によるものですか?

皆様、ここまでご回答いただきありがとうございました。付け加えておきますが、実際にはこのコードを最適化することが目的ではなく (既にかなり最適化されています)、コンパイラ / CPU / アーキテクチャの観点から、そのようなパフォーマンスの違いを説明できるものを理解することがさらに重要です。あなたの答えはとても役に立ちました。

4

4 に答える 4

5

生成されたマシンコードを読まないとコードがどのように最適化されたかを確実に判断できないため、マイクロベンチマークでこれをチェックするのは困難です。RISC スタイルの命令で x86 コードを実際に実行します。

計算には 1 サイクルしかかからず、CPU は一度に最大 3 つの計算を実行できます。L1 キャッシュへのアクセスには 4 サイクルかかり、L2、L3、メイン メモリの場合は 11、40 ~ 75、200 サイクルかかります。

単純な計算を避けるために値を保存すると、多くの場合、実際には遅くなります。ところで、除算とモジュラスを使用することは非常に高価であり、コードを微調整するときにこの値をキャッシュする価値があります。

于 2013-08-15T06:44:45.070 に答える
1

正しい答えは、逆アセンブラー (つまり、.class -> .java コンバーター) で取得できるはずですが、私の推測では、コンパイラーが iref を完全に取り除くことを決定しlength - 1、補助レジスターを格納することを決定した可能性があります。私はどちらかというと C++ 派ですが、まず次のことを試してみたいと思います。

const int refi = length - 1;

for ループ内。また、おそらく使用する必要があります

indexes[ refi ] = 1;
于 2013-08-15T06:49:29.287 に答える
0

まず、プログラムを実行するだけではパフォーマンスを実際に比較することはできません。Java でのマイクロ ベンチマークは複雑です。

また、最新の CPU での減算には、平均でクロック サイクルの 3 分の 1 しかかかりません。3GHz CPU では、0.1 ナノ秒です。また、コンパイラがコードを変更した可能性があるため、減算が実際に行われることは何もわかりません。

そう:

于 2013-08-15T06:54:15.887 に答える