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

J’ai remarqué que j’ai toujours utilisé le double et le double, même si le nombre doit être petit ou grand. Donc, en Java, est-il plus efficace d’utiliser byte ou short au lieu de int et float au lieu de double ?

Donc, supposons que j’ai un programme avec beaucoup d’ints et de doubles. Est-ce que ça vaudrait la peine de passer et de changer mes ints en octets ou shorts si je savais que le nombre correspondrait?

Je sais que java n’a pas de types non signés mais y a-t-il quelque chose en plus que je pourrais faire si je savais que le nombre ne serait que positif?

Par efficacité, je parle surtout du traitement. Je suppose que le ramasse-miettes serait beaucoup plus rapide si toutes les variables avaient la moitié de la taille et que les calculs seraient probablement un peu plus rapides également. (Je suppose que depuis que je travaille sur Android, je dois aussi m’inquiéter de Ram)

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

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

Ai-je tort de supposer que cela devrait être plus rapide et plus efficace? Je détesterais passer et changer tout dans un programme massif pour savoir que j’ai perdu mon temps.

Serait-il utile de le faire dès le début d’un nouveau projet? (Je veux dire, je pense que chaque petite chose aiderait, mais encore une fois, si oui, pourquoi ne semble-t-il pas que quelqu’un le fasse.)

Ai-je tort de supposer que cela devrait être plus rapide et plus efficace? Je détesterais passer et changer tout dans un programme massif pour savoir que j’ai perdu 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 la peine d’ essayer d’optimiser ceci… à moins que vous n’ayez la preuve claire que l’optimisation est nécessaire. Et si vous devez optimiser l’utilisation de la mémoire pour les champs d’object en particulier, vous devrez probablement prendre d’autres mesures (plus efficaces).

Réponse plus longue

La machine virtuelle Java modélise les stacks et les champs d’object en utilisant des décalages qui sont (en effet) des multiples d’une taille de cellule primitive de 32 bits. Ainsi, lorsque vous déclarez une variable locale ou un champ object comme un byte (disons), la variable / le champ sera stocké dans une cellule de 32 bits, comme un int .

Il y a deux exceptions à cela:

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

Il pourrait donc être 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 JIT pourrait être en mesure de l’optimiser, mais en pratique, je n’ai jamais entendu parler d’un JIT. L’un des obstacles est que le JIT ne peut généralement pas s’exécuter tant que les instances de la classe en cours de compilation n’ont pas été créées. Si JIT optimise la disposition de la mémoire, vous pourriez avoir deux (ou plus) “saveurs” d’object de la même classe … et cela présenterait d’énormes difficultés.


Revisitation

En regardant les résultats des tests dans la réponse de @ meriton, il apparaît que l’utilisation de short et d’ 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 sanction est importante. (Vous ne devriez pas … mais c’est une autre affaire.)

Je pense que l’explication est que JIT fait probablement les multiplications en utilisant des instructions de multiplication 32 bits dans chaque cas. Mais dans l’ byte et le short cas, il exécute des instructions supplémentaires pour convertir la valeur 32 bits intermédiaire en un byte ou un shortshort 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 indique un autre problème avec le passage à short et à byte en tant qu’optimisation. Cela pourrait empirer les performances … dans un algorithme arithmétique et calcul intensif.

Cela dépend de l’implémentation de la machine virtuelle Java, ainsi que du matériel sous-jacent. La plupart des matériels modernes ne récupèrent pas les octets simples de la mémoire (ou même du cache de premier niveau), c’est-à-dire que l’utilisation des types primitifs plus petits ne réduit généralement pas la consommation de bande passante 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 éliminant les bits supplémentaires, ce qui n’est pas plus rapide non plus.

Le seul avantage est que les types primitifs plus petits peuvent entraîner une disposition 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 de caches du cache) et réduit la surcharge de la récupération de place.

En règle générale cependant, 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 Ssortingng name; public Benchmark(Ssortingng 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 Ssortingng toSsortingng() { return name + "\t" + time() + " ns"; } public static void main(Ssortingng[] 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 vieux carnet de notes:

 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.

L’utilisation de l’octet au lieu de l’int peut augmenter les performances si vous les utilisez en grande quantité. 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(Ssortingng[] 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'un nouveau TestClass. Chaque test le fait 20 millions de fois et il y a 50 tests.

Voici le 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 finalement obtenu ceci:

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

Maintenant, je change les ints en octets dans TestClass et je l'exécute à nouveau. Voici le résultat:

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

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

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

Dans un environnement “pur”, qui n’est pas Java car toutes les implémentations d’octets et de longs, et les shorts, et d’autres choses amusantes vous sont généralement cachées, l’octet permet de mieux utiliser l’espace.

Cependant, votre ordinateur n’est probablement pas 8 bits, et ce n’est probablement pas 16 bits. cela signifie que pour obtenir 16 ou 8 bits en particulier, il faudrait recourir à la «supercherie», ce qui fait perdre du temps pour prétendre avoir la possibilité d’accéder à ces types lorsque cela est nécessaire.

À 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 en morceaux qui sont confortables pour votre processeur. Un processeur 64 bits aime traiter avec des éléments 64 bits, et tout ce qui est inférieur à cela nécessite souvent de la «magie technique» pour faire croire qu’il aime les traiter.

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 dont vous vous souciez est que votre programme fonctionne et que substituer un float à un int ne nuise pas à l’exactitude, je ne vois aucun avantage à choisir l’un ou l’autre sauf si vous pouvez démontrer que l’utilisation des deux types modifie les performances. Les performances de réglage basées sur des types différents en 2 ou 3 octets sont vraiment la dernière chose à laquelle vous devez vous intéresser. Donald Knuth a dit un jour: “L’optimisation prématurée est la racine de tout mal” (pas sûr que ce soit lui, éditer si vous avez la réponse).