104

数値がどれほど小さくても大きくても、常に int と double を使用してきたことに気付きました。では、Javaでは、andの代わりにbyteorを使用する方が効率的ですか?shortintfloatdouble

したがって、int と double を多く含むプログラムがあるとします。数値が適合することがわかっている場合、int をバイトまたは short に変更する価値はありますか?

Java には unsigned 型がないことはわかっていますが、数値が正の数だけであることがわかっている場合にできることはありますか?

効率的とは、主に処理を意味します。すべての変数が半分のサイズになり、その計算もおそらく多少速くなると、ガベージコレクターははるかに高速になると思います。(私はAndroidで作業しているので、RAMについてもいくらか心配する必要があると思います)

(ガベージコレクターはプリミティブではなくオブジェクトのみを処理しますが、放棄されたオブジェクトのすべてのプリミティブを削除すると思いますよね?)

私が持っている小さなAndroidアプリで試してみましたが、違いはまったくわかりませんでした. (私は「科学的に」何も測定していませんが。)

より高速で効率的であると仮定するのは間違っていますか? 大規模なプログラムのすべてを変更して時間を無駄にしたことがわかるのは嫌です。

新しいプロジェクトを始めるとき、最初からやり直す価値はありますか? (つまり、少しでも役立つと思いますが、もしそうなら、なぜ誰もがそれをしているように見えないのですか. )

4

6 に答える 6

122

より高速で効率的であると仮定するのは間違っていますか? 大規模なプログラムのすべてを変更して時間を無駄にしたことがわかるのは嫌です。

簡潔な答え

はい、あなたは間違っています。ほとんどの場合、使用されるスペースに関してほとんど違いはありません。

これを最適化しようとする価値はありません...最適化が必要であるという明確な証拠がない限り。また、特にオブジェクト フィールドのメモリ使用を最適化する必要がある場合は、おそらく他の (より効果的な) 対策を講じる必要があります。

より長い答え

Java 仮想マシンは、(実際には) 32 ビットのプリミティブ セル サイズの倍数であるオフセットを使用して、スタックとオブジェクト フィールドをモデル化します。したがって、ローカル変数またはオブジェクト フィールドを (たとえば) a として宣言するbyteと、変数/フィールドはint.

これには 2 つの例外があります。

  • longおよびdouble値には、2 つのプリミティブ 32 ビット セルが必要です。
  • プリミティブ型の配列はパック形式で表現されるため、(たとえば) バイト配列は 32 ビット ワードあたり 4 バイトを保持します。

そのため、 and ... とプリミティブの大きな配列の使用を最適化する価値があるかもしれません。しかし、一般的にいいえ。longdouble

理論的には、JITはこれを最適化できるかもしれませんが、実際には JIT で最適化できるとは聞いたことがありません。障害の 1 つは、通常、コンパイル中のクラスのインスタンスが作成されるまで JIT を実行できないことです。JIT がメモリ レイアウトを最適化した場合、同じクラスのオブジェクトの 2 つ (またはそれ以上) の「フレーバー」が存在する可能性があり、それは大きな困難をもたらすでしょう。


再訪

@meriton の回答のベンチマーク結果を見ると、 and の代わりにshortandを使用すると、乗算のパフォーマンスが低下するようです。実際、操作を分離して考えると、ペナルティは重大です。(それらを個別に考えるべきではありません...しかし、それは別のトピックです。)byteint

説明は、JITがおそらくそれぞれの場合に32ビットの乗算命令を使用して乗算を行っているということだと思います。ただし、byteandのshort場合は、追加の命令を実行して中間の 32 ビット値をbyteorshortに変換し、各ループ反復で使用します。(理論的には、その変換はループの最後で一度行うことができます...しかし、オプティマイザがそれを理解できるとは思えません。)

とにかく、これは、最適化としてのshort切り替えに関する別の問題を示しています。byteパフォーマンスが低下する可能性があります...算術および計算集約型のアルゴリズムでは。


二次的な質問

Java には unsigned 型がないことはわかっていますが、数値が正の数だけであることがわかっている場合にできることはありますか?

いいえ、とにかくパフォーマンスの面ではありません。Integer( 、 などには、、などを unsigned としてLong処理するメソッドがいくつかあります。しかし、これらはパフォーマンス上の利点をもたらしません。それが目的ではありません。)intlong

(ガベージコレクターはプリミティブではなくオブジェクトのみを処理しますが、放棄されたオブジェクトのすべてのプリミティブを削除すると思いますよね?)

正しい。オブジェクトのフィールドは、オブジェクトの一部です。オブジェクトがガベージコレクションされると消えます。同様に、配列が収集されると、配列のセルは消えます。フィールドまたはセル型がプリミティブ型の場合、値はフィールド/セルに格納されます...オブジェクト/配列の一部であり、削除されています。

于 2013-01-26T00:08:19.787 に答える
32

これは、JVM の実装と、基盤となるハードウェアによって異なります。最新のハードウェアのほとんどは、メモリから (または第 1 レベルのキャッシュからでも) 1 バイトをフェッチしません。つまり、一般に、より小さいプリミティブ型を使用しても、メモリ帯域幅の消費は削減されません。同様に、最新の CPU のワード サイズは 64 ビットです。それらはより少ないビットで操作を実行できますが、それは余分なビットを破棄することで機能し、これも高速ではありません.

唯一の利点は、プリミティブ型が小さいほどメモリ レイアウトがコンパクトになることです。これは特に配列を使用する場合に顕著です。これによりメモリが節約され、参照の局所性が向上し (キャッシュ ミスの数が減少)、ガベージ コレクションのオーバーヘッドが削減されます。

ただし、一般的に言えば、小さいプリミティブ型を使用しても高速ではありません。

それを実証するために、次のベンチマークを見てください。

public class Benchmark {

    public static void benchmark(String label, Code code) {
        print(25, label);
        
        try {
            for (int iterations = 1; ; iterations *= 2) { // detect reasonable iteration count and warm up the code under test
                System.gc(); // clean up previous runs, so we don't benchmark their cleanup
                long previouslyUsedMemory = usedMemory();
                long start = System.nanoTime();
                code.execute(iterations);
                long duration = System.nanoTime() - start;
                long memoryUsed = usedMemory() - previouslyUsedMemory;
                
                if (iterations > 1E8 || duration > 1E9) { 
                    print(25, new BigDecimal(duration * 1000 / iterations).movePointLeft(3) + " ns / iteration");
                    print(30, new BigDecimal(memoryUsed * 1000 / iterations).movePointLeft(3) + " bytes / iteration\n");
                    return;
                }
            }
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }
    
    private static void print(int desiredLength, String message) {
        System.out.print(" ".repeat(Math.max(1, desiredLength - message.length())) + message);
    }
    
    private static long usedMemory() {
        return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
    }

    @FunctionalInterface
    interface Code {
        /**
         * Executes the code under test.
         * 
         * @param iterations
         *            number of iterations to perform
         * @return any value that requires the entire code to be executed (to
         *         prevent dead code elimination by the just in time compiler)
         * @throws Throwable
         *             if the test could not complete successfully
         */
        Object execute(int iterations);
    }

    public static void main(String[] args) {
        benchmark("long[] traversal", (iterations) -> {
            long[] array = new long[iterations];
            for (int i = 0; i < iterations; i++) {
                array[i] = i;
            }
            return array;
        });
        benchmark("int[] traversal", (iterations) -> {
            int[] array = new int[iterations];
            for (int i = 0; i < iterations; i++) {
                array[i] = i;
            }
            return array;
        });
        benchmark("short[] traversal", (iterations) -> {
            short[] array = new short[iterations];
            for (int i = 0; i < iterations; i++) {
                array[i] = (short) i;
            }
            return array;
        });
        benchmark("byte[] traversal", (iterations) -> {
            byte[] array = new byte[iterations];
            for (int i = 0; i < iterations; i++) {
                array[i] = (byte) i;
            }
            return array;
        });
        
        benchmark("long fields", (iterations) -> {
            class C {
                long a = 1;
                long b = 2;
            }
            
            C[] array = new C[iterations];
            for (int i = 0; i < iterations; i++) {
                array[i] = new C();
            }
            return array;
        });
        benchmark("int fields", (iterations) -> {
            class C {
                int a = 1;
                int b = 2;
            }
            
            C[] array = new C[iterations];
            for (int i = 0; i < iterations; i++) {
                array[i] = new C();
            }
            return array;
        });
        benchmark("short fields", (iterations) -> {
            class C {
                short a = 1;
                short b = 2;
            }
            
            C[] array = new C[iterations];
            for (int i = 0; i < iterations; i++) {
                array[i] = new C();
            }
            return array;
        });
        benchmark("byte fields", (iterations) -> {
            class C {
                byte a = 1;
                byte b = 2;
            }
            
            C[] array = new C[iterations];
            for (int i = 0; i < iterations; i++) {
                array[i] = new C();
            }
            return array;
        });

        benchmark("long multiplication", (iterations) -> {
            long result = 1;
            for (int i = 0; i < iterations; i++) {
                result *= 3;
            }
            return result;
        });
        benchmark("int multiplication", (iterations) -> {
            int result = 1;
            for (int i = 0; i < iterations; i++) {
                result *= 3;
            }
            return result;
        });
        benchmark("short multiplication", (iterations) -> {
            short result = 1;
            for (int i = 0; i < iterations; i++) {
                result *= 3;
            }
            return result;
        });
        benchmark("byte multiplication", (iterations) -> {
            byte result = 1;
            for (int i = 0; i < iterations; i++) {
                result *= 3;
            }
            return result;
        });
    }
}

Intel Core i7 CPU @ 3.5 GHz で OpenJDK 14 を実行すると、次のように出力されます。

     long[] traversal     3.206 ns / iteration      8.007 bytes / iteration
      int[] traversal     1.557 ns / iteration      4.007 bytes / iteration
    short[] traversal     0.881 ns / iteration      2.007 bytes / iteration
     byte[] traversal     0.584 ns / iteration      1.007 bytes / iteration
          long fields    25.485 ns / iteration     36.359 bytes / iteration
           int fields    23.126 ns / iteration     28.304 bytes / iteration
         short fields    21.717 ns / iteration     20.296 bytes / iteration
          byte fields    21.767 ns / iteration     20.273 bytes / iteration
  long multiplication     0.538 ns / iteration      0.000 bytes / iteration
   int multiplication     0.526 ns / iteration      0.000 bytes / iteration
 short multiplication     0.786 ns / iteration      0.000 bytes / iteration
  byte multiplication     0.784 ns / iteration      0.000 bytes / iteration

ご覧のとおり、大きな配列をトラバースする場合にのみ大幅な速度の節約が発生します。小さいオブジェクト フィールドを使用してもメリットはほとんどなく、小さなデータ型では計算が実際にはわずかに遅くなります。

全体として、パフォーマンスの違いはごくわずかです。アルゴリズムの最適化は、プリミティブ型の選択よりもはるかに重要です。

于 2013-01-26T00:50:25.870 に答える
6

それらを大量に使用している場合は、byte代わりに使用するとパフォーマンスが向上します。intここに実験があります:

import java.lang.management.*;

public class SpeedTest {

/** Get CPU time in nanoseconds. */
public static long getCpuTime() {
    ThreadMXBean bean = ManagementFactory.getThreadMXBean();
    return bean.isCurrentThreadCpuTimeSupported() ? bean
            .getCurrentThreadCpuTime() : 0L;
}

public static void main(String[] args) {
    long durationTotal = 0;
    int numberOfTests=0;

    for (int j = 1; j < 51; j++) {
        long beforeTask = getCpuTime();
        // MEASURES THIS AREA------------------------------------------
        long x = 20000000;// 20 millions
        for (long i = 0; i < x; i++) {
                           TestClass s = new TestClass(); 

        }
        // MEASURES THIS AREA------------------------------------------
        long duration = getCpuTime() - beforeTask;
        System.out.println("TEST " + j + ": duration = " + duration + "ns = "
                + (int) duration / 1000000);
        durationTotal += duration;
        numberOfTests++;
    }
    double average = durationTotal/numberOfTests;
    System.out.println("-----------------------------------");
    System.out.println("Average Duration = " + average + " ns = "
            + (int)average / 1000000 +" ms (Approximately)");


}

}

このクラスは、新しい を作成する速度をテストしTestClassます。各テストは 2000 万回実行され、50 回のテストがあります。

TestClass は次のとおりです。

 public class TestClass {
     int a1= 5;
     int a2= 5; 
     int a3= 5;
     int a4= 5; 
     int a5= 5;
     int a6= 5; 
     int a7= 5;
     int a8= 5; 
     int a9= 5;
     int a10= 5; 
     int a11= 5;
     int a12=5; 
     int a13= 5;
     int a14= 5; 
 }

SpeedTestクラスを実行しましたが、最終的には次のようになりました。

 Average Duration = 8.9625E8 ns = 896 ms (Approximately)

現在、TestClass で int をバイトに変更して、再度実行しています。結果は次のとおりです。

 Average Duration = 6.94375E8 ns = 694 ms (Approximately)

この実験は、大量の変数をインスタンス化している場合、int の代わりに byte を使用すると効率が向上することを示していると思います

于 2014-05-12T13:41:43.677 に答える
2

バイトは一般的に 8 ビットと見なされます。short は一般に 16 ビットと見なされます。

バイトとロング、ショートのすべての実装、およびその他の楽しいことが一般的に隠されているため、Javaではない「純粋な」環境では、バイトはスペースをより有効に活用します。

ただし、お使いのコンピューターはおそらく 8 ビットではなく、おそらく 16 ビットではありません。これは、特に 16 ビットまたは 8 ビットを取得するために、必要なときにそれらの型にアクセスできるふりをするために時間を浪費する「策略」に訴える必要があることを意味します。

この時点では、ハードウェアの実装方法によって異なります。ただし、CPU が快適に使用できるチャンクに物を格納することで、最高の速度が得られると私は考えてきました。64 ビット プロセッサは 64 ビット要素を処理するのが好きであり、それ以下のものは、それらを処理するのが好きであるふりをするために「エンジニアリングの魔法」を必要とすることがよくあります。

于 2013-01-26T00:17:44.833 に答える
0

違いはほとんどわかりません!それは、デザイン、適切さ、統一性、習慣などの問題です...時にはそれは単なる好みの問題です. floatプログラムが起動して実行され、 aを a に置き換えてもint正確性が損なわれないことだけを気にする場合、いずれかの型を使用するとパフォーマンスが変わることを実証できない限り、どちらかを選択するメリットはないと思います。2 バイトまたは 3 バイトが異なる型に基づいてパフォーマンスをチューニングすることは、実際に気にする必要のない最後のことです。Donald Knuth はかつて次のように述べています。

于 2013-01-25T21:00:01.670 に答える