java - Does the compiler/JIT recognize redundant checks? -
lets have multiple applications layers, external libraries , java standard libraries , dealing string.
at each , every command when hand in nullpointer, application going throw exception. prevent that, supposed check null object , handle yourself.
now means check null, external libraries check null , java standard libraries check null , potentially low level c code checks null.
isn't redundant ? somehow optimized compiler , branch prediciton handle cases ?
as indicated comments , other answers: there many degrees of freedom , variables involved here. on highest level, refers design principles, aiming @ avoiding "meaningful" null
values, , thus, explicit null
checks. (this difficult sometimes. semantically meaningful cases of null
built standard api). said redundant null
checks can not avoided, because 1 never knows method called. on lower, technical levels, distinction between javac
compiler , jit
pointed out. on lowest level, things branch prediction may come play (this has become remarkably famous due array processing question...).
referring jit, curious 1 case may particularly interesting pattern described - namely, whether redundant null
checks eliminated during method inlining.
i tried create simple test this. it's harder expected create sensible , meaningful test here: idea create very simple version of chain of method calls suggested:
public static int processstringa(dummystring string) { if (string == null) return -2; return processstringb(string); } public static int processstringb(dummystring string) { if (string == null) return -3; return processstringc(string); } public static int processstringc(dummystring string) { if (string == null) return -4; return string.value; }
i spread on several classes, , added "dummy instructions" make less trivial (but still allow inlining), , ran following test eventually:
import java.util.arraylist; import java.util.list; import java.util.random; public class nestednullchecktest { public static void main(string[] args) { (int i=0; i<1000; i++) { runtest(); } } private static void runtest() { list<dummystring> list = createlist(); int blackhole = 0; (dummystring string : list) { blackhole += processstringtest(string); } system.out.println("result "+blackhole); } private static int processstringtest(dummystring string) { if (string == null) { return -1; } return nestednullchecka.processstringa(string); } private static list<dummystring> createlist() { list<dummystring> list = new arraylist<dummystring>(); random random = new random(0); (int i=0; i<100000; i++) { if (random.nextdouble() < 0.1) { list.add(null); } else { list.add(new dummystring(i)); } } return list; } } class dummystring { int value; dummystring(int value) { this.value = value; } } class nestednullchecka { public static int processstringa(dummystring string) { if (string == null) { return -2; } string.value += 1; return nestednullcheckb.processstringb(string); } } class nestednullcheckb { public static int processstringb(dummystring string) { if (string == null) { return -3; } string.value -= 2; return nestednullcheckc.processstringc(string); } } class nestednullcheckc { public static int processstringc(dummystring string) { if (string == null) { return -4; } string.value *= 2; return string.value; } }
running
java -server -xx:+unlockdiagnosticvmoptions -xx:+traceclassloading -xx:+logcompilation -xx:+printassembly nestednullchecktest
eventually spilled out following assembly processstringtest
method:
decoding compiled method 0x00b51488: code: [entry point] [verified entry point] [constants] # {method} {0x3d90046c} 'processstringtest' '(ldummystring;)i' in 'nestednullchecktest' # parm0: ecx = 'dummystring' # [sp+0x10] (sp of caller) 0x00b51580: sub $0xc,%esp 0x00b51586: mov %ebp,0x8(%esp) ;*synchronization entry ; - nestednullchecktest::processstringtest@-1 (line 30) 0x00b5158a: test %ecx,%ecx 0x00b5158c: je 0x00b515a4 ;*ifnonnull ; - nestednullchecktest::processstringtest@1 (line 30) 0x00b5158e: mov 0x8(%ecx),%eax 0x00b51591: shl %eax 0x00b51593: add $0xfffffffe,%eax ;*imul ; - nestednullcheckc::processstringc@13 (line 103) ; - nestednullcheckb::processstringb@18 (line 90) ; - nestednullchecka::processstringa@18 (line 76) ; - nestednullchecktest::processstringtest@7 (line 34) 0x00b51596: mov %eax,0x8(%ecx) ;*putfield value ; - nestednullcheckc::processstringc@14 (line 103) ; - nestednullcheckb::processstringb@18 (line 90) ; - nestednullchecka::processstringa@18 (line 76) ; - nestednullchecktest::processstringtest@7 (line 34) 0x00b51599: add $0x8,%esp 0x00b5159c: pop %ebp 0x00b5159d: test %eax,0x970000 ; {poll_return} 0x00b515a3: ret 0x00b515a4: mov $0xffffffff,%eax 0x00b515a9: jmp 0x00b51599 0x00b515ab: hlt ... 0x00b515bf: hlt [exception handler] [stub code] 0x00b515c0: jmp 0x00af5e40 ; {no_reloc} [deopt handler code] 0x00b515c5: push $0xb515c5 ; {section_word} 0x00b515ca: jmp 0x00adbfc0 ; {runtime_call} 0x00b515cf: hlt
take huge grain of salt - 1 consider artifact of inappropriate test - @ least dummy example, 1 can say:
yes, jit compiler (sometimes) eliminates redundant checks (for example, during method inlining)
there 1 null
check, , 1 return
instruction, returning -1, topmost method call.
one dig through hoptspot code find optimization pass does compaction step, general (somewhat broad) answer jit remarkably smart in many cases, , does eliminate checks "obviously" redundant.
Comments
Post a Comment