3

新しいコンパイラでは、読みやすいコードを書こうとしていることに気付きますが、内部で行われることを望んでいる最適化が実際に行われていない場合、より多くのメモリを消費する可能性があります。このコードを例にとると、非常に単純なケースです

while (scanner.hasNextLine() && !result)
{
    String line = scanner.nextLine();
    result = line.indexOf(searchString) >= 0;
}

(Eclipse Juno、Java 7 を使用して)これが同じバイトコードを生成すると仮定するのは公平でしょうか?

while (scanner.hasNextLine() && !result)
{
    result = scanner.nextLine().indexOf(searchString) >= 0;
}

前者は 2 行のコードですが、2 行目の長さが短くなり、見やすくなります。IMHOしかし、不要な String オブジェクトが作成されることにもなりますか? ないことを願っています...

4

6 に答える 6

7

創造されたものから逃れることはできませんString。それをローカル変数に代入するという事実はここでは無関係であり、実際にはバイトコード レベルではこの事実さえ気付かれません。そのレベルでは、明示的な変数の有無にかかわらず、結果への参照をスタックに配置する必要がありますチェーン内の次のメソッド呼び出しに渡される順序。

不要なStringインスタンスが作成されるという考えは、C などの別の言語に由来する本能に由来する可能性があります。割り当ては、参照を唯一無二の文字列インスタンスにString s = ...コピーするだけです。これは、すべての Java オブジェクトがヒープ上に存在するためです。そのため、オブジェクトを明示的にコピーして、実際に別のインスタンスを含める必要があります。たとえば、 を書いた場合、実際には の不要なインスタンスが作成されます。String line = new String(scanner.nextLine())String

結論として、コードのどのバージョンにも最適化は含まれていないため、文体の好みのみに従って選択してください。

于 2012-11-09T14:03:54.017 に答える
7

いくつかの一般原則:

  • 時期尚早の最適化はかなり無意味です
  • これらのマイナーな状況で読みやすさを確保することは無意味です
  • ほとんどの場合、最適化はアルゴリズムの変更とその複雑さに起因します
  • アルゴリズムではないものを最適化しようとするとき、あなたはコンパイラとして決して優れていません
  • 最適化を確実にする方法は 2 つだけです。バイトコードまたはベンチマークのパフォーマンスを調べることです。それ以外は通常、憶測です。

あなたの特定のケースでは、変数宣言は最適化に関して何も変更しません。どちらの場合も、文字列がインスタンス化さnextLine()れてスタックに配置され、変数に割り当てられるためです(インスタンス変数でない限り、バイトコードでは消えます)有用性はあなたの目だけのためです)何も変わりません。

于 2012-11-09T14:05:10.647 に答える
2

すべての JDK に含まれているプログラムであるJavaクラス ファイル逆アセンブラーについて質問してみませんか?javap

次のソースコードがあります。

public class Foo {

    static void m1(Scanner scanner, String searchString, boolean result) {
        while (scanner.hasNextLine() && !result) {
            String line = scanner.nextLine();
            result = line.indexOf(searchString) >= 0;
        }
    }

    static void m2(Scanner scanner, String searchString, boolean result) {
        while (scanner.hasNextLine() && !result) {
            result = scanner.nextLine().indexOf(searchString) >= 0;
        }
    }
}

逆アセンブラを実行する場合:

javap -c Foo.class

次のバイトコードを取得します。

static void m1(java.util.Scanner, java.lang.String, boolean);
Code:
   0: goto          22
   3: aload_0
   4: invokevirtual #33                 // Method java/util/Scanner.nextLine:()Ljava/lang/String;
   7: astore_3
   8: aload_3
   9: aload_1
  10: invokevirtual #39                 // Method java/lang/String.indexOf:(Ljava/lang/String;)I
  13: iflt          20
  16: iconst_1
  17: goto          21
  20: iconst_0
  21: istore_2
  22: aload_0
  23: invokevirtual #45                 // Method java/util/Scanner.hasNextLine:()Z
  26: ifeq          33
  29: iload_2
  30: ifeq          3
  33: return

static void m2(java.util.Scanner, java.lang.String, boolean);
Code:
   0: goto          20
   3: aload_0
   4: invokevirtual #33                 // Method java/util/Scanner.nextLine:()Ljava/lang/String;
   7: aload_1
   8: invokevirtual #39                 // Method java/lang/String.indexOf:(Ljava/lang/String;)I
  11: iflt          18
  14: iconst_1
  15: goto          19
  18: iconst_0
  19: istore_2
  20: aload_0
  21: invokevirtual #45                 // Method java/util/Scanner.hasNextLine:()Z
  24: ifeq          31
  27: iload_2
  28: ifeq          3
  31: return

2 つのメソッドのバイトコードを比較すると、唯一の違いは、m1次の 2 つの追加命令が含まれていることです。

7: astore_3
8: aload_3

これは、スタックの一番上にあるオブジェクトへの参照をローカル変数に格納するだけで、他には何もありません。

編集:

逆アセンブラーは、メソッドのローカル変数の数も表示できます。

javap -l Foo.class

どの出力:

static void m1(java.util.Scanner, java.lang.String, boolean);
LocalVariableTable:
  Start  Length  Slot  Name   Signature
         0      34     0 scanner   Ljava/util/Scanner;
         0      34     1 searchString   Ljava/lang/String;
         0      34     2 result   Z
         8      14     3  line   Ljava/lang/String;

static void m2(java.util.Scanner, java.lang.String, boolean);
LocalVariableTable:
  Start  Length  Slot  Name   Signature
         0      32     0 scanner   Ljava/util/Scanner;
         0      32     1 searchString   Ljava/lang/String;
         0      32     2 result   Z
}

基本的に、上記の唯一の違いを確認します-m1メソッドはもう1つのローカル変数のみを割り当てます- String line. これ以上オブジェクトを作成することはありません。いずれかの方法で割り当てられたオブジェクトへの参照をもう 1 つ作成するだけです。

于 2012-11-09T14:21:07.760 に答える
2

トピックから外れているようですが、これにはパフォーマンスも含まれます。

while (!result && scanner.hasNextLine())
{
    String line = scanner.nextLine();
    result = line.indexOf(searchString) >= 0;
}
于 2012-11-09T14:37:11.970 に答える
1

あなたの質問に答えるにscanner.nextLine().indexOf(searchString)は、いつ実行されますか。あなたは何をするnextLine()と思いますか?そして、どのオブジェクトindexOf()で実行されると予想されますか?

Stringご想像のとおり、これは;に依存しています。はい、これStringは作成され、はい、これStringは使用されます。それは(あなたの推測に反して)必要です。

変数の宣言 ( String s) と値の代入の操作は、オブジェクトのインスタンス化 ( new String("test")) と比較してコストがかかりません。

言い換えれば、あなたが達成しようとしていることは、役に立たなかったり、パフォーマンスが大幅に向上したりしていません。


ここには 2 つ目の問題があります。これは、開発者全般にとってより深刻な問題です。実際の問題に遭遇せず、このコードがアプリケーションの実行を大幅に遅くする可能性があるという明白な兆候なしに、その種のコードを最適化しようとするのは時期尚早です。

ほとんどの場合、達成したいことから気が散り、最適化のために読みにくいコードを書くことになります (それはまったく最適化ではないかもしれません!)。

あなたの特定のケースでは(以前は誰も言及していなかったので驚きました。これが私がこの回答を書いた理由です)あなたの「最適化された」コードはみんなの生活を悪化させます。

コードが実行され、ある時点ですべてが失敗し、次のNullPointerException行に表示されると想像してください。

    result = scanner.nextLine().indexOf(searchString) >= 0;

何が失敗しているのかを明確に把握する代わりに、そのコードを手動でデバッグして、そうscannerであるnullかどうか、または何らかの理由でnextLine()が返されるかどうかを確認する必要がありますnull

この問題は以前のコードには存在しませんでしたが、早期に最適化し、コードをよりコンパクトにして、いくつかの操作を無駄にしないようにしたいというこの欲求は、コードを全体的に悪化させました。

于 2012-11-09T14:22:26.540 に答える
0

とにかくコンパイラはローカル変数行をインライン化するので、2 つの間に違いはありません。

于 2012-11-09T14:04:34.150 に答える