web-dev-qa-db-fra.com

En Java, est-il plus efficace d’utiliser byte ou short au lieu de int et float au lieu de double?

J'ai remarqué que j'utilisais toujours int et doubls, peu importe la taille de leur nombre. Donc, en Java, est-il plus efficace d'utiliser byte ou short au lieu de int et float au lieu de double?

Supposons donc que j'ai un programme avec beaucoup d'ints et de doubles. Vaut-il la peine de passer à travers et de changer mon entête en octets ou en shorts si je savais que le nombre irait?

Je sais que Java n'a pas de types non signés, mais y a-t-il autre chose que je pourrais faire si je savais que le nombre ne serait que positif?

Par efficace, je parle surtout de traitement. Je suppose que le ramasse-miettes serait beaucoup plus rapide si toutes les variables avaient une taille réduite de moitié et que les calculs seraient probablement un peu plus rapides aussi . )

(Je suppose que le ramasse-miettes ne traite que des objets et non des primitives, mais supprime tout de même toutes les primitives des objets abandonnés, n'est-ce pas?)

Je l'ai essayé avec une petite application Android mais je n'ai pas vraiment remarqué de différence. (Bien que je n'ai rien "scientifiquement" mesuré.) 

Est-ce que je me trompe en supposant que cela devrait être plus rapide et plus efficace? Je détesterais tout changer dans un programme gigantesque pour découvrir que je perdais mon temps.

Cela vaut-il la peine de le faire dès le début lorsque je lance un nouveau projet? (Je veux dire, je pense que chaque petit geste aiderait, mais encore une fois si c'est le cas, pourquoi ne semble-t-il pas que quelqu'un le fait?)

79
DisibioAaron

Est-ce que je me trompe en supposant que cela devrait être plus rapide et plus efficace? Je détesterais tout changer dans un programme massif pour découvrir que je perdais mon temps.

Réponse courte

Oui, vous avez tort Dans la plupart des cas, cela fait peu de différence en termes d’espace utilisé.

Cela ne vaut pas d'essayer de l'optimiser ... à moins que vous ne disposiez d'une preuve claire qu'une optimisation est nécessaire. Et si vous avez besoin pour optimiser l'utilisation de la mémoire des champs d'objet en particulier, vous devrez probablement prendre d'autres mesures (plus efficaces).

Réponse plus longue

La machine virtuelle Java modélise les piles et les champs d'objet à l'aide de décalages qui sont (en réalité) des multiples d'une taille de cellule primitive de 32 bits. Ainsi, lorsque vous déclarez une variable locale ou un champ d’objet en tant que (disons) une byte, la variable/le champ sera stocké dans une cellule de 32 bits, exactement comme un int.

Il y a deux exceptions à cela:

  • Les valeurs long et double nécessitent 2 cellules 32 bits primitives
  • les tableaux de types primitifs sont représentés sous forme compactée, de sorte que (par exemple) un tableau d'octets contient 4 octets par mot de 32 bits.

Il est donc intéressant d'optimiser l'utilisation de long et double ... et de grands tableaux de primitives. Mais en général non.

En théorie, un EJI pourrait être en mesure de l'optimiser, mais en pratique, je n'ai jamais entendu parler d'un EJS qui le fasse. L'un des obstacles est que le JIT ne peut généralement pas s'exécuter avant la création des instances de la classe en cours de compilation. Si le JIT optimisait la disposition de la mémoire, vous pourriez avoir deux (ou plus) "variantes" d'objet de la même classe ... et cela présenterait d'énormes difficultés.


Revisitation

En regardant les résultats de référence dans la réponse de @ meriton, il apparaît que l'utilisation de short et byte au lieu de int entraîne une pénalité de performance pour la multiplication. En effet, si vous considérez les opérations isolément, la pénalité est importante. (Vous ne devriez pas ... mais c'est une autre affaire.)

Je pense que l'explication est que JIT effectue probablement les multiplications en utilisant des instructions de multiplication 32 bits dans chaque cas. Mais dans le cas byte et short, il exécute des instructions supplémentaires pour convertir la valeur intermédiaire de 32 bits en une variable byte ou short dans chaque itération de boucle. (En théorie, cette conversion pourrait être effectuée une fois à la fin de la boucle ... mais je doute que l'optimiseur puisse le comprendre.)

Quoi qu'il en soit, cela pose un autre problème avec le passage à short et byte comme optimisation. Cela pourrait aggraver les performances ... dans un algorithme arithmétique et de calcul intensif.

96
Stephen C

Cela dépend de la mise en œuvre de la machine virtuelle Java, ainsi que du matériel sous-jacent. La plupart des matériels modernes ne récupéreront pas un seul octet dans la mémoire (ou même dans le cache de premier niveau), c'est-à-dire que l'utilisation de types primitifs plus petits ne réduit généralement pas la consommation de bande passante en mémoire. De même, les processeurs modernes ont une taille de mot de 64 bits. Ils peuvent effectuer des opérations sur moins de bits, mais cela fonctionne en supprimant les bits supplémentaires, ce qui n'est pas plus rapide non plus.

Le seul avantage est que les types primitifs plus petits peuvent générer une structure de mémoire plus compacte, notamment lors de l'utilisation de tableaux. Cela économise de la mémoire, ce qui peut améliorer la localité de référence (réduisant ainsi le nombre d'erreurs dans le cache) et de réduire les coûts liés à la récupération de place. 

Cependant, en règle générale, l’utilisation des types primitifs plus petits n’est pas plus rapide. 

Pour démontrer cela, voici le repère suivant:

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

qui imprime sur mon carnet un peu vieux:

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

Comme vous pouvez le constater, les différences de performances sont assez mineures. L'optimisation des algorithmes est beaucoup plus importante que le choix du type primitif.

28
meriton

Utiliser byte au lieu de int peut augmenter les performances si vous les utilisez énormément. Voici une expérience:

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


}

}

Cette classe teste la vitesse de création d'une nouvelle TestClass. Chaque test le fait 20 millions de fois et il y a 50 tests.

Voici la 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; 

 }

J'ai couru la classe SpeedTest et à la fin j'ai eu ceci:

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

Maintenant, je change les entiers en octets dans TestClass et je les réexécute. Voici le résultat:

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

Je crois que cette expérience montre que si vous installez une quantité énorme de variables, utiliser byte au lieu de int peut augmenter l'efficacité 

4
WVrock

octet est généralement considéré comme 8 bits . short est généralement considéré comme étant 16 bits.

Dans un environnement "pur", qui n'est pas Java, car toute implémentation d'octets et de long, de shorts et autres choses amusantes est généralement cachée de votre part, octet utilise mieux l'espace.

Cependant, votre ordinateur n’est probablement pas en 8 bits et ce n’est probablement pas en 16 bits. cela signifie que, pour obtenir 16 ou 8 bits en particulier, il faudrait recourir à une "ruse" qui fait perdre du temps afin de prétendre qu'il est capable d'accéder à ces types en cas de besoin.

À ce stade, cela dépend de la manière dont le matériel est implémenté. Cependant, d'après ce que j'ai appris, .__, la meilleure vitesse est obtenue en stockant des éléments dans des blocs faciles à utiliser par votre processeur. Un processeur 64 bits aime traiter les éléments 64 bits, et rien de moins que cela nécessite souvent de la "magie de l'ingénierie" pour prétendre qu'il aime les utiliser.

2
Dmitry

La différence est à peine perceptible! C'est plus une question de design, de pertinence, d'uniformité, d'habitude, etc. Parfois, c'est juste une question de goût. Lorsque tout ce qui vous intéresse, c’est que votre programme démarre et que substituer un float à un int ne nuirait pas à la correction, je ne vois aucun avantage à choisir l’un ou l’autre, à moins que vous ne puissiez démontrer que l’utilisation de ce type altère les performances. Régler les performances en fonction de types différents sur 2 ou 3 octets est vraiment la dernière chose à laquelle vous devriez vous intéresser; Donald Knuth a dit un jour: "L'optimisation prématurée est la racine de tout mal" (je ne suis pas sûr que ce soit lui, modifiez-le si vous avez la réponse).

0
saadtaame

L'une des raisons pour lesquelles short/byte/char est moins performant est le manque de prise en charge directe de ces types de données. Par support direct, cela signifie que les spécifications de la JVM ne mentionnent aucun jeu d'instructions pour ces types de données. Des instructions comme stocker, charger, ajouter, etc. ont des versions pour le type de données int. Mais ils n'ont pas de version pour short/byte/char. Par exemple. considérez ci-dessous le code Java:

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

Idem est converti en code machine comme ci-dessous.

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

Maintenant, envisagez de changer int en short comme ci-dessous.

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

Le code machine correspondant va changer comme suit:

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

Comme vous pouvez le constater, pour manipuler un type de données court, il utilise toujours la version d'instruction de type de données int et convertit explicitement int en short si nécessaire. De ce fait, les performances sont réduites.

Maintenant, la raison invoquée pour ne pas accorder un soutien direct est la suivante:

La machine virtuelle Java fournit le support le plus direct pour les données de tapez int. Ceci est en partie en prévision d’implémentations efficaces des piles d'opérandes et de la variable locale de la machine virtuelle Java tableaux. Il est également motivé par la fréquence des données int dans typique programmes. D'autres types intégraux ont un support moins direct. Il n'y a pas octets, caractères ou versions abrégées des instructions store, load ou add, par exemple.

Cité dans la spécification JVM présente ici (Page 58).

0
Manish Bansal