Perché Java ha un'istruzione bytecode IINC?


Perché Java ha un'istruzione bytecode IINC? Esiste già un'istruzione bytecode IADD. Quindi perché IINC esiste?

Author: user2997204, 2016-05-05

3 answers

Solo i progettisti originali di Java possono rispondere perché hanno preso particolari decisioni di progettazione. Tuttavia, possiamo speculare:

IINC non ti consente di fare nulla che non possa già essere realizzato da una combinazione ILOAD/SIPUSH/IADD/ISTORE. La differenza è che IINC è una singola istruzione, che richiede solo 3 o 6 byte, mentre la sequenza di istruzioni 4 è ovviamente più lunga. Quindi IINC riduce leggermente la dimensione del bytecode che lo utilizza.

Oltre a ciò, le prime versioni di Java utilizzavano un interperter, dove ogni istruzione ha un overhead durante l'esecuzione. In questo caso, l'utilizzo di una singola istruzione IINC potrebbe essere più veloce della sequenza di bytecode alternativa equivalente. Si noti che JITting ha reso questo in gran parte irrilevante, ma IINC risale alla versione originale di Java.

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

Guardando questa tabella , ci sono un paio di importanti differenze.

Iinc: incrementa la variabile locale # index per byte const firmato

  1. iinc utilizza un registro al posto dello stack.
  2. iincpuò aumentare solo di un valore di byte firmato. Se si desidera aggiungere [-128,127] a un numero intero, è possibile utilizzare iinc , ma non appena si desidera aggiungere un numero al di fuori di tale intervallo è necessario utilizzare isub, iadd, oppure istruzioni multiple iinc.

E1:

TL;DR

Avevo fondamentalmente ragione, tranne che il limite è firmato valori brevi (16 bit [-32768,32767]). C'è un'istruzione bytecode wide che modifica iinc (e un paio di altre istruzioni) per usare numeri a 16 bit invece di numeri a 8 bit.

Inoltre, considerare l'aggiunta di due variabili insieme. Se una delle variabili non è costante, il compilatore non sarà mai in grado di inline il suo valore in bytecode, quindi impossibile usare iinc; dovrà usare iadd.


package SO37056714;

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

Ho intenzione di sperimentare con il pezzo di codice sopra. Così com'è, è usa iinc, come previsto.

$ 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 usa iinc come previsto.

$ 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 non usa più iinc, ma invece 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 utilizza anche 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
}

Il suffisso _w si riferisce al bytecode wide, che consente costanti fino a 16 bit ([-32768, 32767]).

Se proviamo i += 32768, noi vedrà quello che ho predetto sopra:

$ 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
}

Inoltre, considera il caso in cui stiamo aggiungendo un'altra variabile a i (i += c). Il compilatore non sa se c è costante o meno, quindi non può inline il valore di c in bytecode. Userà iadd anche per questo caso:

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

Come già sottolineato una singola istruzione iinc è più breve della a iload, sipush, iadd, istore sequenza. Ci sono anche prove che l'esecuzione di una riduzione delle dimensioni del codice common-case è stata una motivazione importante.

Esistono istruzioni specializzate per gestire le prime quattro variabili locali, ad esempio aload_0 fa lo stesso di aload 0 e verrà usato spesso per caricare il riferimento this sullo stack degli operando. C'è un'istruzione ldc che può fare riferimento a una dei primi 255 elementi di pool costanti mentre tutti possono essere gestiti da ldc_w, le istruzioni di branch usano due byte per gli offset, quindi solo metodi eccessivamente grandi devono ricorrere a goto_w, e le istruzioni iconst_n per -1 a 5 esistono nonostante tutti possano essere gestiti da bipush che supporta valori che tutti potrebbero anche essere gestiti da sipush, che potrebbero essere sostituiti da ldc.

Quindi le istruzioni asimmetriche sono la norma. Nelle applicazioni tipiche, ci sono molti piccoli metodi con solo poche variabili locali e numeri più piccoli sono più comuni di numeri più grandi. iinc è un equivalente diretto alle espressioni i++ o i+=smallConstantNumber stand-alone (applicate alle variabili locali) che spesso si verificano all'interno dei loop. Essendo in grado di esprimere idiomi di codice comuni in codice più compatto senza perdere la possibilità di esprimere tutto il codice, otterrai grandi risparmi nella dimensione complessiva del codice.

Come anche già sottolineato, c'è solo una piccola opportunità per un'esecuzione più rapida in interpreted esecuzioni che sono irrilevanti per l'esecuzione di codice compilato/ottimizzato.

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