21

私はこの小さな(そして残酷に非効率的な)クラスを作成し、JavaVisualVMを使用してそれをプロファイリングしたいと思いました。

public class Test {

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        br.readLine();
        int n = Integer.parseInt(args[0]);
        int fib = fib(n);
        System.out.println(fib);
    }

    private static int fib(int n) {
        if (n < 2) {
            return n;
        }
        return fib(n-1)+fib(n-2);
    }
}

結果は奇妙です。結果は、ConnectionHandler.run()の呼び出しによって完全に支配されます。

(98.2%)sun.rmi.transport.tcp.TCPTransport $ ConnectionHandler.run()
(1.7%)java.lang.Thread.join(long)
(0%)java.lang.String.equals(Object)
など。 。

プロファイルされたメソッドはおそらく約100あり、そのうちの1つはfib(int)ではありません。

私のプログラムが実際にすべての時間をこれらの方法に費やしているとは考えられません。それらは私のjvmに接続し、そのことを実行しているプロファイラーのようです。

私は何が間違っているのですか?

わかりやすくするために編集: nに45を渡すと、このアプリケーションは20秒間実行されます。私が最初にプロファイリングしていたプログラム(フィボナッチ計算機ではない)は、CPUの4つのコアすべてを100%でペグし、最大5分間のプロファイリング実行を実行していました。これらは同じ結果であり、私のアプリケーションのメソッドはホットスポットメソッドリストの上位に表示されませんでした。

実行ごとに異なりますが、ConnectionHandler.run()が常に最上位にあり、通常、プロファイル時間の約99%を占めます。

2番目の編集:サンプラーを使用してみましたが、JProfilerが生成しているものと一致する結果が得られています。これの欠点は、プロファイリングに付属するスタックトレース情報を取得できないことです。しかし、私の差し迫ったニーズにとって、これは素晴らしいことです。

遊んでいるときに私が発見したことは、VisualVMがメソッド呼び出しの実時間をプロファイリング中にカウントすることです。

私の特定のケースでは、私のアプリケーションには、ワーカースレッドを起動し、キューでのメッセージの待機をすぐにブロックするメインスレッドがあります。

これは、私のCPUを消費しているのはこのメソッドではないという事実にもかかわらず、ブロッキングメソッドがプロファイラーでほとんどすべての時間を費やしているように見えることを意味します。

同じことがsun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run()メソッドにも当てはまると思いますが、このメソッドは正常に機能しますが、終了すると、アプリケーションで最も長く実行されるメソッドの1つになります

4

4 に答える 4

3

まったく考えられないことだと思います。「ペイロード」が非常に小さいアプリケーションがあり (もちろん、それは の値に依存しますn)、必要な追加の労力 (プロファイラーを接続してすべての情報をそこに移動する) が圧倒されることを受け入れる必要があります。そのペイロード。

fibこれは、最初にプロファイリングするようなアプリケーションではありません。なぜなら、 ( の自明でない値の場合) とにかく膨大な時間が費やされることは明らかであり、nそれを最適化の明らかなターゲットとしてマークするからです。

次のようなより実質的なアプリケーションには、プロファイラーを使用する傾向があります。

  • 最適化の努力をどこに向けるべきかは明らかではありません。と
  • ペイロードで実行するかなりの量の作業があります。

そのコードを本当にテストしたい場合は、おそらく次のように置き換えて、その効果を高める必要があります。

int fib = fib(n);

と:

for (int i = 0; i < 100000; i++) {
    int fib = fib(n);
)

ただ、一つ気をつけていただきたいことがあります。特定の JVM の内部構造はわかりませんが、引数の削減が遅い再帰的な方法を使用することは、通常は悪い考えであり、スタック領域がすぐに使い果たされてしまいます。

つまり、バイナリ検索は、各再帰レベルで残りの検索スペースの半分を削除するため、良い候補です (したがって、10 億項目の検索スペースはわずか 30 レベルです)。

一方、数値 1,000,000,000 のフィボナッチ数列に再帰を使用すると、約 10 億レベルかかり、ほとんどのスタックはそれを含むのに苦労します。

末尾再帰の最適化はその問題を回避するかもしれませんが、最適化が行われていない場合には注意が必要です。

于 2011-03-22T02:22:29.007 に答える
1

Ron の回答に基づいて、開始直後に JVM を停止し、プロファイラーをアクティブにして、最後に (Enter キーを押して) 実行を続行することで、結果を改善することができました。粗雑です。

class Foobar {
    /* First line in Class */
      static {
        try {
            System.in.read();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    /* .. */
    public static void main(..) {
        doMagic()
    }
}        
于 2012-04-10T16:50:20.053 に答える
1

jvisualvm プロファイリングは、おそらくロード時にバイトコードをクラスに織り込みます。プログラムにはクラスが 1 つしかなく、jvisualvm がシーンに到着するまでにすでに初期化されているため、インストルメント化できないと思います。

fib メソッドを別のクラスに移動して、プロファイリングを再試行してください。jvisualvm で CPU プロファイリングを有効にする前に、jvm オプション "-verbose:class" を追加して、クラスがロードされていないことを再確認することができます。

編集:JBさん、コメントありがとうございます。私のクラスローディングのホグウォッシュを忘れてください。私の直感では、fib メソッドはメイン メソッドと緊密に結合されすぎているため、実際には現在実行されているバイトコードです。

于 2011-03-22T03:43:43.857 に答える
0

私の推測では、fib に渡す値が小さすぎて、プログラムが登録するのに十分な時間実行されていないということです。プロファイリング (またはベンチマーク) 時にほぼすべての意味のあるデータを取得するには、通常、少なくとも数秒の経過時間が必要です。

于 2011-03-22T02:21:49.733 に答える