web-dev-qa-db-de.com

Ist es in Java effizienter, Byte oder Short statt Int und Float statt Double zu verwenden?

Ich habe bemerkt, dass ich immer int verwendet habe und verdoppelt, egal wie klein oder groß die Anzahl sein muss. Ist es in Java also effizienter, byte oder short anstelle von int und float anstelle von double zu verwenden?

Nehmen wir an, ich habe ein Programm mit vielen Ints und Doubles. Lohnt es sich, durchzugehen und meine Ints in Bytes oder Shorts zu ändern, wenn ich wüsste, dass die Anzahl passen würde?

Ich weiß, dass Java keine unsignierten Typen hat, aber gibt es noch etwas, was ich tun könnte, wenn ich wüsste, dass die Zahl nur positiv ist?

Mit effizient meine ich meistens Verarbeitung. Ich würde davon ausgehen, dass der Garbage-Collector viel schneller wäre, wenn alle Variablen nur die halbe Größe haben würden und dass Berechnungen wahrscheinlich auch etwas schneller wären. (Ich denke, da ich an Android arbeite, muss ich mich auch etwas um RAM kümmern.) )

(Ich würde davon ausgehen, dass der Garbage Collection nur Objekte behandelt und nicht primitiv, aber trotzdem alle primitiven Objekte in verlassenen Objekten löscht, oder?)

Ich habe es mit einer kleinen Android-App ausprobiert, die ich aber nicht wirklich bemerkt habe. (Obwohl ich nichts "wissenschaftlich" gemessen habe.) 

Bin ich falsch in der Annahme, dass es schneller und effizienter sein sollte? Ich würde es hassen, alles in einem riesigen Programm durchzugehen und zu ändern, um herauszufinden, dass ich meine Zeit verschwendet habe.

Lohnt es sich von Anfang an, wenn ich ein neues Projekt beginne? (Ich meine, ich denke, jedes bisschen würde helfen, aber wenn ja, warum sieht es nicht so aus, als ob jemand es tut.)

79
DisibioAaron

Liege ich falsch, wenn ich annehme, dass es schneller und effizienter sein sollte? Ich würde es hassen, alles in einem riesigen Programm zu ändern, um herauszufinden, dass ich meine Zeit verschwendet habe.

Kurze Antwort

Ja, du liegst falsch. In den meisten Fällen macht es wenig Unterschied in Bezug auf den verwendeten Platz.

Es ist nicht wert , dies zu optimieren ... es sei denn, Sie haben eindeutige Beweise dafür, dass eine Optimierung erforderlich ist. Und wenn Sie benötigen , um insbesondere die Speichernutzung von Objektfeldern zu optimieren, müssen Sie wahrscheinlich andere (effektivere) Maßnahmen ergreifen.

Längere Antwort

Die virtuelle Maschine Java modelliert Stapel und Objektfelder unter Verwendung von Offsets, die (tatsächlich) Vielfache einer primitiven 32-Bit-Zellengröße sind. Wenn Sie also eine lokale Variable oder ein Objektfeld als byte deklarieren, wird die Variable/das Feld wie ein int in einer 32-Bit-Zelle gespeichert.

Hiervon gibt es zwei Ausnahmen:

  • long und double erfordern 2 primitive 32-Bit-Zellen
  • arrays von primitiven Typen werden in gepackter Form dargestellt, so dass (zum Beispiel) ein Array von Bytes 4 Bytes pro 32-Bit-Wort enthält.

Es könnte sich also lohnen , die Verwendung von long und double ... und großen Arrays von Primitiven zu optimieren . Aber im Allgemeinen nicht.

Theoretisch könnte eine JIT dies optimieren , aber in der Praxis habe ich noch nie von einer JIT gehört, die dies tut. Ein Hindernis besteht darin, dass die JIT in der Regel erst ausgeführt werden kann, nachdem Instanzen der zu kompilierenden Klasse erstellt wurden. Wenn das JIT das Speicherlayout optimieren würde, könnten Sie zwei (oder mehr) "Aromen" von Objekten derselben Klasse haben ... und das würde enorme Schwierigkeiten bereiten.


Überarbeitung

Bei Betrachtung der Benchmark-Ergebnisse in @ meritons Antwort scheint die Verwendung von short und byte anstelle von int eine Leistungsminderung für die Multiplikation mit sich zu bringen. In der Tat ist die Strafe erheblich, wenn Sie die Operationen isoliert betrachten. (Du solltest nicht ... aber das ist eine andere Sache.)

Ich denke, die Erklärung ist, dass JIT wahrscheinlich die Multiplikationen mit jeweils 32-Bit-Multiplikationsbefehlen durchführt. In den Fällen byte und short werden jedoch zusätzliche Anweisungen ausgeführt, um den 32-Bit-Zwischenwert in einen byte oder short umzuwandeln ) _ in jeder Schleifeniteration. (Theoretisch könnte diese Konvertierung einmal am Ende der Schleife erfolgen ... aber ich bezweifle, dass der Optimierer dies herausfinden kann.)

Auf jeden Fall deutet dies auf ein anderes Problem beim Umschalten auf short und byte als Optimierung hin. Dies könnte die Leistung in einem arithmetischen und rechenintensiven Algorithmus verschlechtern .

96
Stephen C

Das hängt von der Implementierung der JVM sowie der zugrunde liegenden Hardware ab. Die meiste moderne Hardware holt keine einzelnen Bytes aus dem Speicher (oder sogar aus dem Cache der ersten Ebene), d. H. Die Verwendung der kleineren primitiven Typen verringert im Allgemeinen den Speicherbandbreitenverbrauch nicht. Ebenso haben moderne CPUs eine Word-Größe von 64 Bit. Sie können Operationen mit weniger Bits ausführen, aber dies führt dazu, dass die zusätzlichen Bits verworfen werden, was auch nicht schneller ist.

Der einzige Vorteil ist, dass kleinere primitive Typen zu einem kompakteren Speicherlayout führen können, vor allem bei der Verwendung von Arrays. Dies spart Speicherplatz, wodurch die Referenzlokalität verbessert werden kann (wodurch die Anzahl der Cache-Fehler verringert wird) und der Aufwand für die Sammlung von Speicherbereinigungen verringert wird. 

Im Allgemeinen ist die Verwendung der kleineren primitiven Typen jedoch nicht schneller. 

Um dies zu demonstrieren, beachten Sie den folgenden Benchmark:

package tools.bench;

import Java.math.BigDecimal;

public abstract class Benchmark {

    final String name;

    public Benchmark(String name) {
        this.name = name;
    }

    abstract int run(int iterations) throws Throwable;

    private BigDecimal time() {
        try {
            int nextI = 1;
            int i;
            long duration;
            do {
                i = nextI;
                long start = System.nanoTime();
                run(i);
                duration = System.nanoTime() - start;
                nextI = (i << 1) | 1; 
            } while (duration < 100000000 && nextI > 0);
            return new BigDecimal((duration) * 1000 / i).movePointLeft(3);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }   

    @Override
    public String toString() {
        return name + "\t" + time() + " ns";
    }

    public static void main(String[] args) throws Exception {
        Benchmark[] benchmarks = {
            new Benchmark("int multiplication") {
                @Override int run(int iterations) throws Throwable {
                    int x = 1;
                    for (int i = 0; i < iterations; i++) {
                        x *= 3;
                    }
                    return x;
                }
            },
            new Benchmark("short multiplication") {                   
                @Override int run(int iterations) throws Throwable {
                    short x = 0;
                    for (int i = 0; i < iterations; i++) {
                        x *= 3;
                    }
                    return x;
                }
            },
            new Benchmark("byte multiplication") {                   
                @Override int run(int iterations) throws Throwable {
                    byte x = 0;
                    for (int i = 0; i < iterations; i++) {
                        x *= 3;
                    }
                    return x;
                }
            },
            new Benchmark("int[] traversal") {                   
                @Override int run(int iterations) throws Throwable {
                    int[] x = new int[iterations];
                    for (int i = 0; i < iterations; i++) {
                        x[i] = i;
                    }
                    return x[x[0]];
                }
            },
            new Benchmark("short[] traversal") {                   
                @Override int run(int iterations) throws Throwable {
                    short[] x = new short[iterations];
                    for (int i = 0; i < iterations; i++) {
                        x[i] = (short) i;
                    }
                    return x[x[0]];
                }
            },
            new Benchmark("byte[] traversal") {                   
                @Override int run(int iterations) throws Throwable {
                    byte[] x = new byte[iterations];
                    for (int i = 0; i < iterations; i++) {
                        x[i] = (byte) i;
                    }
                    return x[x[0]];
                }
            },
        };
        for (Benchmark bm : benchmarks) {
            System.out.println(bm);
        }
    }
}

was auf meinem etwas alten Notizbuch druckt:

int multiplication  1.530 ns
short multiplication    2.105 ns
byte multiplication 2.483 ns
int[] traversal 5.347 ns
short[] traversal   4.760 ns
byte[] traversal    2.064 ns

Wie Sie sehen, sind die Leistungsunterschiede recht gering. Die Optimierung von Algorithmen ist weitaus wichtiger als die Wahl des primitiven Typs.

28
meriton

Die Verwendung von Byte anstelle von Int kann die Leistung erhöhen, wenn Sie sie in großem Umfang verwenden. Hier ist ein Experiment:

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)");


}

}

Diese Klasse testet die Geschwindigkeit beim Erstellen einer neuen TestClass. Jeder Test macht 20 Millionen Mal und es gibt 50 Tests.

Hier ist die Testklasse:

 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; 

 }

Ich habe die SpeedTest-Klasse ausgeführt und am Ende Folgendes erhalten:

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

Jetzt ändere ich die Ints in Bytes in der TestClass und führe sie erneut aus. Hier ist das Ergebnis:

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

Ich glaube, dass dieses Experiment zeigt, dass die Verwendung von Bytes anstelle von Int die Effizienz steigern kann, wenn Sie eine große Anzahl von Variablen erzeugen 

4
WVrock

byte wird im Allgemeinen als 8 Bit betrachtet. _. Short wird im Allgemeinen als 16 Bit betrachtet.

In einer "reinen" Umgebung, die nicht Java ist, da die Implementierung von Bytes und Longs sowie von Shorts und anderen lustigen Dingen im Allgemeinen für Sie verborgen ist, nutzt Byte den Speicherplatz besser.

Ihr Computer ist jedoch wahrscheinlich nicht 8-Bit und wahrscheinlich auch nicht 16-Bit. Dies bedeutet, dass, um insbesondere 16 oder 8 Bits zu erhalten, auf "Trickserei" zurückgegriffen werden muss, was Zeit verschwendet, um so zu tun, als könne er bei Bedarf auf diese Typen zugreifen.

An diesem Punkt hängt es davon ab, wie Hardware implementiert wird. Von mir wurde gesagt, dass die beste Geschwindigkeit erreicht wird, wenn Dinge in Chunks gespeichert werden, die für die Verwendung der CPU geeignet sind. Ein 64-Bit-Prozessor mag es, mit 64-Bit-Elementen umzugehen, und alles andere als das erfordert oft "technische Magie", um so zu tun, als würde er mit ihnen umgehen.

2
Dmitry

Der Unterschied ist kaum spürbar! Es ist eher eine Frage des Designs, der Angemessenheit, der Einheitlichkeit, der Gewohnheit usw. ... Manchmal ist es nur eine Frage des Geschmacks. Wenn Sie sich nur darum kümmern, dass Ihr Programm läuft und eine float durch eine int ersetzt wird, die der Korrektheit nicht schadet, sehe ich keinen Vorteil darin, den einen oder anderen zu wählen, wenn Sie nicht nachweisen können, dass die Verwendung eines der beiden Typen die Leistung verändert. Das Optimieren der Leistung basierend auf Typen, die sich in 2 oder 3 Bytes unterscheiden, ist wirklich das Letzte, worauf Sie achten sollten. Donald Knuth hat einmal gesagt: "Vorzeitige Optimierung ist die Wurzel allen Übels" (nicht sicher, ob er es war, bearbeiten Sie, wenn Sie die Antwort haben).

0
saadtaame

Ein Grund dafür, dass short/byte/char weniger leistungsfähig ist, ist der fehlende direkte Support für diese Datentypen. Direkter Support bedeutet, dass die JVM-Spezifikationen keinen Befehlssatz für diese Datentypen enthalten. Anweisungen wie Speichern, Laden, Hinzufügen usw. haben Versionen für den Datentyp int. Sie haben jedoch keine Versionen für kurzes Byte/Zeichen. Z.B. Beachten Sie den folgenden Java-Code:

void spin() {
 int i;
 for (i = 0; i < 100; i++) {
 ; // Loop body is empty
 }
}

Das gleiche wird in den Maschinencode wie folgt umgewandelt.

0 iconst_0 // Push int constant 0
1 istore_1 // Store into local variable 1 (i=0)
2 goto 8 // First time through don't increment
5 iinc 1 1 // Increment local variable 1 by 1 (i++)
8 iload_1 // Push local variable 1 (i)
9 bipush 100 // Push int constant 100
11 if_icmplt 5 // Compare and loop if less than (i < 100)
14 return // Return void when done

Überlegen Sie nun, int in short wie folgt zu ändern.

void sspin() {
 short i;
 for (i = 0; i < 100; i++) {
 ; // Loop body is empty
 }
}

Der entsprechende Maschinencode ändert sich wie folgt:

0 iconst_0
1 istore_1
2 goto 10
5 iload_1 // The short is treated as though an int
6 iconst_1
7 iadd
8 i2s // Truncate int to short
9 istore_1
10 iload_1
11 bipush 100
13 if_icmplt 5
16 return

Wie Sie feststellen können, wird zur Bearbeitung des kurzen Datentyps immer noch die Befehlsversion des Datentyps int verwendet, und bei Bedarf wird explizit int in short konvertiert. Dadurch wird die Leistung reduziert.

Nun wurde der Grund angeführt, dass er keine direkte Unterstützung gewährt,

Die Java Virtual Machine bietet die direkteste Unterstützung für Daten von Typ int. Dies ist zum Teil in Erwartung effizienter Implementierungen der Operandenstapel und der lokalen Variablen der Java Virtual Machine Arrays Es wird auch durch die Häufigkeit von int-Daten in typischen .__ motiviert. Programme. Andere integrale Typen haben weniger direkte Unterstützung. Es gibt keine Byte-, Zeichen- oder Kurzversionen des Speichers, Laden oder Hinzufügen von Anweisungen, zum Beispiel.

Zitat aus JVM-Spezifikation vorhanden hier (Seite 58).

0
Manish Bansal