Будет ли законным для компилятора Java пропускать коды операций getfield после первого доступа?
Я экспериментировал с портом Java некоторого кода C#, и я был удивлен, увидев, что javac 1.8.0_60 испускает getfield
код операции каждый раз, когда к объектному полю обращались.
Вот код Java:
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;
}
}
Как сообщает javap, javac 1.8.0_60 выдает следующий байт-код:
public byte [] ToByteArray (); Код: 0: aload_0 1: getfield #3 // Знак поля:I 4: ИФН 15 7: iconst_1 8: байт newarray 10: дуп 11: iconst_0 12: iconst_0 13: Бастор 14: возвращение 15: iconst_0 16: istore_2 17: aload_0 18: getfield #2 // Биты поля:[I 21: ifnonnull 48 24: aload_0 25: getfield #3 // Знак поля:I 28: ifge 37 31: Сипуш 255 34: перейти к 38 37: iconst_0 38: i2b 39: istore_1 40: нагрузка_0 41: getfield #3 // Знак поля:I 44: istore_3 45: перейти к 193 48: aload_0 49: getfield #3 // Знак поля:I 52: iconst_m1 53: if_icmpne 156 56: iconst_m1 57: istore_1 58: getstatiC#11 // Поле $assertionsDisabled:Z 61: ифне 80 64: aload_0 65: getfield #2 // Биты поля:[I 68: длина массива 69: ifgt 80 72: новый #12 // класс java/lang/AssertionError 75: дуп 76: invokespecial #13 // Метод java/lang/AssertionError."":()V 79: Атроу 80: getstatiC#11 // Поле $assertionsDisabled:Z 83: ИФН 109 86: aload_0 87: getfield #2 // Биты поля:[I 90: aload_0 91: getfield #2 // Биты поля:[I 94: длина массива 95: iconst_1 96: isub 97: Iaload 98: ИФН 109 101: новый #12 // класс java/lang/AssertionError 104: дуп 105: invokespecial #13 // Метод java/lang/AssertionError."":()V 108: атроу 109: нагрузка_0 110: getfield #2 // Биты поля:[I 113: iload_2 114: Iaload 115: ИФН 124 118: iinc 2, 1 121: переход к 109 124: нагрузка_0 125: getfield #2 // Биты поля:[I 128: aload_0 129: getfield #2 // Биты поля:[I 132: длина массива 133: iconst_1 134: isub 135: iaload 136: iconst_m1 137: иксор 138: istore_3 139: нагрузка_0 140: getfield #2 // Биты поля:[I 143: длина массива 144: iconst_1 145: isub 146: iload_2 147: if_icmpne 193 150: Iinc 3, 1 153: перейти к 193 156: getstatiC#11 // Поле $ assertionsDisabled: Z 159: ИФН 178 162: нагрузка_0 163: getfield # 3 // Знак поля: I 166: iconst_1 167: if_icmpeq 178 170: новый #12 // класс java / lang / AssertionError 173: дуп 174: invokespecial # 13 // Метод java/lang/AssertionError."":()V 177: Атроу 178: iconst_0 179: istore_1 180: aload_0 181: getfield #2 // Биты поля:[I 184: aload_0 185: getfield #2 // Биты поля:[I 188: длина массива 189: iconst_1 190: isub 191: Iaload 192: istore_3 193: iload_3 194: Бипуш 24 196: иушр 197: i2b 198: дуп 199: магазин 4 201: iload_1 202: if_icmpeq 211 205: iconst_3 206: магазин 5 208: перейти к 254 211: iload_3 212: бипуш 16 214: иушр 215: i2b 216: дуп 217: магазин 4 219: iload_1 220: if_icmpeq 229 223: iconst_2 224: магазин 5 226: перейти к 254 229: iload_3 230: bipush 8 232: иушр 233: i2b 234: дуп 235: магазин 4 237: iload_1 238: if_icmpeq 247 241: iconst_1 242: магазин 5 244: перейти к 254 247: iload_3 248: i2b 249: магазин 4 251: iconst_0 252: магазин 5 254: iload 4 256: sipush 128 259: ианд 260: iload_1 261: Сипуш 128 264: ианд 265: if_icmpeq 272 268: iconst_1 269: перейти к 273 272: iconst_0 273: магазин 6 275: iconst_0 276: магазин 8 278: нагрузка_0 279: getfield #2 // Биты поля:[I 282: ifnonnull 325 285: iload 5 287: iconst_1 288: IADD 289: Илоад 6 291: Ифек 298 294: iconst_1 295: перейти к 299 298: iconst_0 299: IADD 300: байт newarray 302: Астор 7 304: getstatiC#11 // Поле $ assertionsDisabled: Z 307: ИФН 443 310: нагрузка 7 312: длина массива 313: iconst_4 314: if_icmple 443 317: новый #12 // класс java / lang / AssertionError 320: дуп 321: invokespecial # 13 // Метод java/lang/AssertionError."":()V 324: Атроу 325: iconst_4 326: aload_0 327: getfield #2 // Биты поля:[I 330: длина массива 331: iconst_1 332: isub 333: imul 334: iload 5 336: IADD 337: iconst_1 338: IADD 339: iload 6 341: Ифек 348 344: iconst_1 345: перейти к 349 348: iconst_0 349: IADD 350: байт newarray 352: Астор 7 354: iconst_0 355: магазин 9 357: илоад 9 359: aload_0 360: getfield #2 // Биты поля:[I 363: длина массива 364: iconst_1 365: isub 366: if_icmpge 443 369: aload_0 370: getfield #2 // Биты поля:[I 373: Илоад 9 375: Iaload 376: магазин 10 378: нагрузка_0 379: getfield # 3 // Знак поля: I 382: iconst_m1 383: if_icmpne 404 386: Илоад 10 388: iconst_m1 389: иксор 390: магазин 10 392: Илоад 9 394: iload_2 395: if_icmpgt 404 398: илоад 10 400: iconst_1 401: IADD 402: магазин 10 404: iconst_0 405: магазин 11 407: iload 11 409: iconst_4 410: if_icmpge 437 413: нагрузка 7 415: iload 8 417: iinc 8, 1 420: iload 10 422: i2b 423: Бастор 424: Илоад 10 426: бипуш 8 428: иушр 429: магазин 10 431: ИНК 11, 1 434: перейти к 407 437: ИНК 9, 1 440: перейти к 357 443: iconst_0 444: магазин 9 446: Илоад 9 448: iload 5 450: if_icmpgt 474 453: нагрузка 7 455: iload 8 457: iinc 8, 1 460: iload_3 461: i2b 462: Бастор 463: iload_3 464: бипуш 8 466: иушр 467: istore_3 468: ИНК 9, 1 471: переход к 446 474: iload 6 476: Ифек 488 479: нагрузка 7 481: нагрузка 7 483: длина массива 484: iconst_1 485: isub 486: iload_1 487: Бастор 488: нагрузка 7 490: возвращение
Обратите внимание, что getfield
код операции выдавался компилятором каждый раз, когда sign
а также bits
поля были доступны.
Читая §17.4.5, "Порядок до и после", JLS8, я не понимаю, почему было бы необходимо испускать getfield
код операции каждый раз, когда sign
а также bits
Доступ к полям (кроме первого раза).
Будет ли законным для компилятора Java испускать только два getfield
коды операций и сохранить видимые тогда значения полей в кадре локальных переменных?
1 ответ
Это не только законно, но и, вероятно, произойдет после того, как код скомпилирован компилятором JIT (поднятие выражения является одной из доступных оптимизаций).
Например код ниже:
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");
}
}
быстро завершается (по крайней мере, на моей машине). Это не гарантируется, но поскольку поле выбирается каждый раз, существует момент, когда изменение распространяется и перехватывается.
Если я раскомментирую Thread.sleep(1000)
тем не менее, программа никогда не заканчивается, потому что JIT имеет достаточно времени для оптимизации кода и замены stop
жестко закодированным значением, т.е. false
,