2015-10-07 12 views
5

Ich experimentierte mit einem Java-Port von some C# code und ich war überrascht zu sehen, dass javac 1.8.0_60 einen getfield Opcode ausgab, jedes Mal, wenn ein Objektfeld zugegriffen wurde.Wäre es für einen Java-Compiler zulässig, getfield-Opcodes nach dem ersten Zugriff wegzulassen?

ist die Java-Code:

public class BigInteger 
{ 
    private int[] bits; 
    private int sign; 

    //... 

    public byte[] ToByteArray() 
    { 
     if (sign == 0) 
     { 
      return new byte[] { 0 }; 
     } 

     byte highByte; 
     int nonZeroDwordIndex = 0; 
     int highDword; 
     if (bits == null) 
     { 
      highByte = (byte)((sign < 0) ? 0xff : 0x00); 
      highDword = sign; 
     } 
     else if (sign == -1) 
     { 
      highByte = (byte)0xff; 
      assert bits.length > 0; 
      assert bits[bits.length - 1] != 0; 
      while (bits[nonZeroDwordIndex] == 0) 
      { 
       nonZeroDwordIndex++; 
      } 

      highDword = ~bits[bits.length - 1]; 
      if (bits.length - 1 == nonZeroDwordIndex) 
      { 
       highDword += 1; 
      } 
     } 
     else 
     { 
      assert sign == 1; 
      highByte = 0x00; 
      highDword = bits[bits.length - 1]; 
     } 

     byte msb; 
     int msbIndex; 
     if ((msb = (byte)(highDword >>> 24)) != highByte) 
     { 
      msbIndex = 3; 
     } 
     else if ((msb = (byte)(highDword >>> 16)) != highByte) 
     { 
      msbIndex = 2; 
     } 
     else if ((msb = (byte)(highDword >>> 8)) != highByte) 
     { 
      msbIndex = 1; 
     } 
     else 
     { 
      msb = (byte)highDword; 
      msbIndex = 0; 
     } 

     boolean needExtraByte = (msb & 0x80) != (highByte & 0x80); 
     byte[] bytes; 
     int curByte = 0; 
     if (bits == null) 
     { 
      bytes = new byte[msbIndex + 1 + (needExtraByte ? 1 : 0)]; 
      assert bytes.length <= 4; 
     } 
     else 
     { 
      bytes = new byte[4 * (bits.length - 1) + msbIndex + 1 + (needExtraByte ? 1 : 0)]; 

      for (int i = 0; i < bits.length - 1; i++) 
      { 
       int dword = bits[i]; 
       if (sign == -1) 
       { 
        dword = ~dword; 
        if (i <= nonZeroDwordIndex) 
        { 
         dword = dword + 1; 
        } 
       } 
       for (int j = 0; j < 4; j++) 
       { 
        bytes[curByte++] = (byte)dword; 
        dword >>>= 8; 
       } 
      } 
     } 
     for (int j = 0; j <= msbIndex; j++) 
     { 
      bytes[curByte++] = (byte)highDword; 
      highDword >>>= 8; 
     } 
     if (needExtraByte) 
     { 
      bytes[bytes.length - 1] = highByte; 
     } 
     return bytes; 
    } 
} 

Wie javap berichtet, javac 1.8.0_60 erzeugt folgende Bytecode:

 
    public byte[] ToByteArray(); 
    Code: 
     0: aload_0 
     1: getfield  #3     // Field sign:I 
     4: ifne   15 
     7: iconst_1 
     8: newarray  byte 
     10: dup 
     11: iconst_0 
     12: iconst_0 
     13: bastore 
     14: areturn 
     15: iconst_0 
     16: istore_2 
     17: aload_0 
     18: getfield  #2     // Field bits:[I 
     21: ifnonnull  48 
     24: aload_0 
     25: getfield  #3     // Field sign:I 
     28: ifge   37 
     31: sipush  255 
     34: goto   38 
     37: iconst_0 
     38: i2b 
     39: istore_1 
     40: aload_0 
     41: getfield  #3     // Field sign:I 
     44: istore_3 
     45: goto   193 
     48: aload_0 
     49: getfield  #3     // Field sign:I 
     52: iconst_m1 
     53: if_icmpne  156 
     56: iconst_m1 
     57: istore_1 
     58: getstatic  #11     // Field $assertionsDisabled:Z 
     61: ifne   80 
     64: aload_0 
     65: getfield  #2     // Field bits:[I 
     68: arraylength 
     69: ifgt   80 
     72: new   #12     // class java/lang/AssertionError 
     75: dup 
     76: invokespecial #13     // Method java/lang/AssertionError."":()V 
     79: athrow 
     80: getstatic  #11     // Field $assertionsDisabled:Z 
     83: ifne   109 
     86: aload_0 
     87: getfield  #2     // Field bits:[I 
     90: aload_0 
     91: getfield  #2     // Field bits:[I 
     94: arraylength 
     95: iconst_1 
     96: isub 
     97: iaload 
     98: ifne   109 
    101: new   #12     // class java/lang/AssertionError 
    104: dup 
    105: invokespecial #13     // Method java/lang/AssertionError."":()V 
    108: athrow 
    109: aload_0 
    110: getfield  #2     // Field bits:[I 
    113: iload_2 
    114: iaload 
    115: ifne   124 
    118: iinc   2, 1 
    121: goto   109 
    124: aload_0 
    125: getfield  #2     // Field bits:[I 
    128: aload_0 
    129: getfield  #2     // Field bits:[I 
    132: arraylength 
    133: iconst_1 
    134: isub 
    135: iaload 
    136: iconst_m1 
    137: ixor 
    138: istore_3 
    139: aload_0 
    140: getfield  #2     // Field bits:[I 
    143: arraylength 
    144: iconst_1 
    145: isub 
    146: iload_2 
    147: if_icmpne  193 
    150: iinc   3, 1 
    153: goto   193 
    156: getstatic  #11     // Field $assertionsDisabled:Z 
    159: ifne   178 
    162: aload_0 
    163: getfield  #3     // Field sign:I 
    166: iconst_1 
    167: if_icmpeq  178 
    170: new   #12     // class java/lang/AssertionError 
    173: dup 
    174: invokespecial #13     // Method java/lang/AssertionError."":()V 
    177: athrow 
    178: iconst_0 
    179: istore_1 
    180: aload_0 
    181: getfield  #2     // Field bits:[I 
    184: aload_0 
    185: getfield  #2     // Field bits:[I 
    188: arraylength 
    189: iconst_1 
    190: isub 
    191: iaload 
    192: istore_3 
    193: iload_3 
    194: bipush  24 
    196: iushr 
    197: i2b 
    198: dup 
    199: istore  4 
    201: iload_1 
    202: if_icmpeq  211 
    205: iconst_3 
    206: istore  5 
    208: goto   254 
    211: iload_3 
    212: bipush  16 
    214: iushr 
    215: i2b 
    216: dup 
    217: istore  4 
    219: iload_1 
    220: if_icmpeq  229 
    223: iconst_2 
    224: istore  5 
    226: goto   254 
    229: iload_3 
    230: bipush  8 
    232: iushr 
    233: i2b 
    234: dup 
    235: istore  4 
    237: iload_1 
    238: if_icmpeq  247 
    241: iconst_1 
    242: istore  5 
    244: goto   254 
    247: iload_3 
    248: i2b 
    249: istore  4 
    251: iconst_0 
    252: istore  5 
    254: iload   4 
    256: sipush  128 
    259: iand 
    260: iload_1 
    261: sipush  128 
    264: iand 
    265: if_icmpeq  272 
    268: iconst_1 
    269: goto   273 
    272: iconst_0 
    273: istore  6 
    275: iconst_0 
    276: istore  8 
    278: aload_0 
    279: getfield  #2     // Field bits:[I 
    282: ifnonnull  325 
    285: iload   5 
    287: iconst_1 
    288: iadd 
    289: iload   6 
    291: ifeq   298 
    294: iconst_1 
    295: goto   299 
    298: iconst_0 
    299: iadd 
    300: newarray  byte 
    302: astore  7 
    304: getstatic  #11     // Field $assertionsDisabled:Z 
    307: ifne   443 
    310: aload   7 
    312: arraylength 
    313: iconst_4 
    314: if_icmple  443 
    317: new   #12     // class java/lang/AssertionError 
    320: dup 
    321: invokespecial #13     // Method java/lang/AssertionError."":()V 
    324: athrow 
    325: iconst_4 
    326: aload_0 
    327: getfield  #2     // Field bits:[I 
    330: arraylength 
    331: iconst_1 
    332: isub 
    333: imul 
    334: iload   5 
    336: iadd 
    337: iconst_1 
    338: iadd 
    339: iload   6 
    341: ifeq   348 
    344: iconst_1 
    345: goto   349 
    348: iconst_0 
    349: iadd 
    350: newarray  byte 
    352: astore  7 
    354: iconst_0 
    355: istore  9 
    357: iload   9 
    359: aload_0 
    360: getfield  #2     // Field bits:[I 
    363: arraylength 
    364: iconst_1 
    365: isub 
    366: if_icmpge  443 
    369: aload_0 
    370: getfield  #2     // Field bits:[I 
    373: iload   9 
    375: iaload 
    376: istore  10 
    378: aload_0 
    379: getfield  #3     // Field sign:I 
    382: iconst_m1 
    383: if_icmpne  404 
    386: iload   10 
    388: iconst_m1 
    389: ixor 
    390: istore  10 
    392: iload   9 
    394: iload_2 
    395: if_icmpgt  404 
    398: iload   10 
    400: iconst_1 
    401: iadd 
    402: istore  10 
    404: iconst_0 
    405: istore  11 
    407: iload   11 
    409: iconst_4 
    410: if_icmpge  437 
    413: aload   7 
    415: iload   8 
    417: iinc   8, 1 
    420: iload   10 
    422: i2b 
    423: bastore 
    424: iload   10 
    426: bipush  8 
    428: iushr 
    429: istore  10 
    431: iinc   11, 1 
    434: goto   407 
    437: iinc   9, 1 
    440: goto   357 
    443: iconst_0 
    444: istore  9 
    446: iload   9 
    448: iload   5 
    450: if_icmpgt  474 
    453: aload   7 
    455: iload   8 
    457: iinc   8, 1 
    460: iload_3 
    461: i2b 
    462: bastore 
    463: iload_3 
    464: bipush  8 
    466: iushr 
    467: istore_3 
    468: iinc   9, 1 
    471: goto   446 
    474: iload   6 
    476: ifeq   488 
    479: aload   7 
    481: aload   7 
    483: arraylength 
    484: iconst_1 
    485: isub 
    486: iload_1 
    487: bastore 
    488: aload   7 
    490: areturn 

Beachten Sie, dass ein getfield Opcode vom Compiler jedes Mal emittiert wurde dass auf die Felder und bits zugegriffen wurde.

Lese §17.4.5, Happen-Vor Auftrag von JLS8, ich sehe nicht, warum es wäre ein getfield Opcode jedes Mal, wenn die sign und bits Felder zugegriffen werden (außer dem ersten) emittieren erforderlich.

Wäre es für einen Java-Compiler zulässig, nur zwei getfield Opcodes auszugeben und die dann sichtbaren Werte der Felder in lokalen Variablen des Rahmens zu speichern?

Antwort

4

Nicht nur ist es legal, aber es wird wahrscheinlich passieren, sobald der Code vom JIT-Compiler kompiliert wird (Expression-Hotsing ist eine der verfügbaren Optimierungen).

Zum Beispiel des folgenden Code:

public class Test { 
    private boolean stop; 

    public static void main(String[] args) throws InterruptedException { 
    Test t = new Test(); 
    new Thread(t::m).start(); 
// Thread.sleep(1000); 
    System.out.println("stop is now true"); 
    t.stop = true; 
    } 

    private void m() { 
    while (!stop); 
    System.out.println("Finished"); 
    } 

} 

endet sofort (auf meiner Maschine zumindest). Dies ist nicht garantiert, aber da das Feld jedes Mal abgerufen wird, gibt es einen Punkt, an dem die Änderung verbreitet und abgefangen wird.

Wenn ich jedoch Thread.sleep(1000) auskommentiere, endet das Programm nie, weil der JIT genug Zeit hat, den Code zu optimieren und stop durch einen fest codierten Wert zu ersetzen, d. H. false.

+1

Vielen Dank für Ihre Antwort. Ich habe [jitwatch] (https://github.com/AdoptOpenJDK/jitwatch) an einem Programm ausprobiert, das ToByteArray() wiederholt an verschiedenen Eingängen aufruft und zwischen den Läufen schläft. Der JIT kompiliert die Methode während eines Testlaufs viermal neu. In der endgültigen Kompilierung erhält die x86-64-Assembly die Werte der Felder 'sign' und 'bits' nur einmal. Ich habe auch eine Version getestet, in der ich explizit lokale Variablen verwendet habe. Die x86-64-Assembly dieser "local" -Version erhält auch die Werte der Felder "sign" und "bits" nur einmal. Interessanterweise ist die "members" Version 34 Asm Bytes kürzer. –

+0

@JohnM "* Der Optimierer geht nicht davon aus, dass eine Variable sich niemals ändert, nur weil sie keine Sekunde hat *" => das ist nicht, was ich sage. 'stop' wird niemals in' m' modifiziert und es ist eine lokale, nicht volatile Variable. So kann der Optimierer "m" wie folgt neu schreiben: 'boolean localStop = stop; while (! localStop); '(und es tut). Wenn "Stop" "volatil" gewesen wäre, wäre das keine rechtliche Änderung. Der Sinn des Schlafes ist es, dem JIT genug Zeit zu geben, um den Code zu kompilieren und zu kompilieren. – assylias

Verwandte Themen