Pourquoi Java a - t-il une instruction de bytecode IINC?


Pourquoi Java a-t-il une instruction de bytecode IINC? Il existe déjà une instruction IADD bytecode. Alors pourquoi l'IINC existe-t-il?

Author: user2997204, 2016-05-05

3 answers

Seuls les concepteurs originaux de Java peuvent répondre pourquoi ils ont pris des décisions de conception particulières. Cependant, nous pouvons spéculer:

IINC ne vous permet pas de faire tout ce qui ne peut pas déjà être accompli par un combo ILOAD/SIPUSH/IADD/ISTORE. La différence est que IINC est une seule instruction, qui ne prend que 3 ou 6 octets, tandis que la séquence de 4 instructions est évidemment plus longue. IINC réduit donc légèrement la taille du bytecode qui l'utilise.

En dehors de cela, les premières versions de Java utilisaient un interperter, où chaque instruction a une surcharge pendant l'exécution. Dans ce cas, l'utilisation d'une seule instruction IINC pourrait être plus rapide que la séquence de bytecode alternative équivalente. Notez que le JITting a rendu cela largement hors de propos, mais IINC remonte à la version originale de Java.

 2
Author: Antimony, 2016-05-06 01:54:57

Regardant ce tableau, il existe quelques différences importantes.

Iinc: incrémenter la variable locale # index par octet signé const

  1. iinc utilise s'inscrire au lieu de la pile.
  2. iincne peut incrémenter que par une valeur d'octet signé. Si vous souhaitez ajouter [-128,127] pour un entier, alors vous pouvez utiliser iinc, mais dès que vous voulez ajouter un numéro à l'extérieur de cette plage, vous devrez utiliser isub, iadd, ou instructions multiples iinc.

E1:

TL; DR

J'avais fondamentalement raison, sauf que la limite est signée valeurs courtes (16 bits [-32768,32767]). Il y a une instruction de bytecode wide qui modifie iinc (et quelques autres instructions) pour utiliser des nombres de 16 bits au lieu de nombres de 8 bits.

De plus, envisagez d'ajouter deux variables ensemble. Si l'une des variables n'est pas constante, le compilateur ne pourra jamais intégrer sa valeur au bytecode, donc il ne peut pas utiliser iinc, il va falloir utiliser iadd.


package SO37056714;

public class IntegerIncrementTest {
  public static void main(String[] args) {
    int i = 1;
    i += 5;
  }
}

Je vais expérimenter avec le morceau de code ci-dessus. En l'état, is utilise iinc, comme prévu.

$ javap -c IntegerIncrementTest.class 
Compiled from "IntegerIncrementTest.java"
public class SO37056714.IntegerIncrementTest {
  public SO37056714.IntegerIncrementTest();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_1
       1: istore_1
       2: iinc          1, 5
       5: return
}

i += 127 utilise iinc comme prévu.

$ javap -c IntegerIncrementTest.class 
Compiled from "IntegerIncrementTest.java"
public class SO37056714.IntegerIncrementTest {
  public SO37056714.IntegerIncrementTest();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_1
       1: istore_1
       2: iinc          1, 127
       5: return
}

i += 128 n'utilise plus iinc, mais à la place iinc_w:

$ javap -c IntegerIncrementTest.class 
Compiled from "IntegerIncrementTest.java"
public class SO37056714.IntegerIncrementTest {
  public SO37056714.IntegerIncrementTest();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_1
       1: istore_1
       2: iinc_w        1, 128
       8: return
}

i -= 601 aussi utilise iinc_w:

$ javap -c IntegerIncrementTest.class 
Compiled from "IntegerIncrementTest.java"
public class SO37056714.IntegerIncrementTest {
  public SO37056714.IntegerIncrementTest();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_1
       1: istore_1
       2: iinc_w        1, -601
       8: return
}

Le suffixe _w fait référence au bytecode wide, qui permet des constantes jusqu'à 16 bits ([-32768, 32767]).

Si nous essayons i += 32768, nous verra ce que j'ai prédit ci-dessus:

$ javap -c IntegerIncrementTest.class 
Compiled from "IntegerIncrementTest.java"
public class SO37056714.IntegerIncrementTest {
  public SO37056714.IntegerIncrementTest();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_1
       1: istore_1
       2: iload_1
       3: ldc           #16                 // int 32768
       5: iadd
       6: istore_1
       7: return
}

De plus, considérons le cas où nous ajoutons une autre variable à i (i += c). Le compilateur ne sait pas si c est constant ou non, il ne peut donc pas insérer la valeur de c dans le bytecode. Il utilisera iadd pour ce cas:

int i = 1;
byte c = 3;
i += c;
$ javap -c IntegerIncrementTest.class 
Compiled from "IntegerIncrementTest.java"
public class SO37056714.IntegerIncrementTest {
  public SO37056714.IntegerIncrementTest();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_1
       1: istore_1
       2: iconst_3
       3: istore_2
       4: iload_1
       5: iload_2
       6: iadd
       7: istore_1
       8: return
}
 1
Author: Jeffrey, 2016-05-05 18:41:01

, Comme déjà souligné la seule iinc instruction est plus courte que l'un iload, sipush, iadd, istore la séquence. Il existe également des preuves, que l'exécution d'une réduction de la taille du code de cas commun était une motivation importante.

Il existe des instructions spécialisées pour traiter les quatre premières variables locales, par exemple aload_0 fait la même chose que aload 0 et il sera souvent utilisé pour charger la référence this sur la pile d'opérandes. Il y a une instruction ldc pouvant faire référence à un sur les 255 premiers éléments de pool constant alors qu'ils pourraient tous être gérés par ldc_w, les instructions de branche utilisent deux octets pour les décalages, donc seules les méthodes trop grandes doivent recourir à goto_w, et les instructions iconst_n pour -1 à 5 existent malgré cela, toutes pourraient être gérées par bipush qui prend en charge les valeurs qui pourraient toutes également être gérées par sipush, qui pourraient être remplacées par ldc.

Les instructions asymétriques sont donc la norme. Dans les applications typiques, il existe de nombreuses petites méthodes avec seulement quelques variables locales et des nombres plus petits sont plus communs que des nombres plus grands. iinc est un équivalent direct des expressions autonomes i++ ou i+=smallConstantNumber (appliquées aux variables locales) qui se produisent souvent dans les boucles. En étant capable d'exprimer des idiomes de code communs dans un code plus compact sans perdre la possibilité d'exprimer tout le code, vous obtiendrez de grandes économies dans la taille globale du code.

Comme déjà souligné, il n'y a qu'une légère opportunité d'exécution plus rapide en interprétation exécutions qui ne sont pas pertinentes pour l'exécution de code compilé/optimisé.

 1
Author: Holger, 2017-05-23 12:23:58