Reputation: 20520
Suppose I have a method that declares an array of primitives, but doesn't use it:
public void frobnicate() {
int[] pointless = new int[1000];
System.out.println("bored");
}
What will happen to my pointless
array? I think that javac will keep it in (though I'm uncertain). If so, what will the JIT compiler do? Will it get rid of it completely? Will it create the array but not a reference to it?
There are murky waters here. It's not quite the same as an unused
int pointless = 23;
which could be easily removed. With an array, there's an object instance being created, and it's then being zeroed out too. Can the JIT compiler elide all of this? Or will all the work still be done?
The motivation for this, by the way, is that I'm writing a benchmark that looks at the effect of different buffer sizes. I would like to remove the effect of the allocation of space for different sizes of buffer. Internally, the buffer is just a byte[]
; so the easiest way to remove the effect of the memory allocation would be to start the method by creating an unused array, so that the total amount allocated is the same in each case. In other words, if the maximum buffer size I'll test is 4MB, then when I test with a 1MB buffer, I start by creating an unused byte[3*1024*1024]
, so that the total allocated is still 4MB. (I'm prepared to ignore the few bytes of extra overhead, which should be insignificant.)
But obviously there's no point trying to allocate the unused array if it's never going to make it past the JIT compiler...
Upvotes: 4
Views: 182
Reputation: 3917
Java microbenchmarks are difficult. However, a quick look through the bytecode for frobincate
shows that javac (OpenJDK 7) does not elide the array...
Compiled from "Frobincate.java"
public class Frobincate {
public Frobincate();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void frobincate();
Code:
0: sipush 1000
3: newarray int
5: astore_0
6: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
9: ldc #3 // String bored
11: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
14: return
}
Digging into the assembly also shows the object (array allocation) is not elided by the jvm...
[Disassembling for mach='i386:x86-64']
[Entry Point]
[Verified Entry Point]
[Constants]
# {method} 'frobincate' '()V' in 'Foo'
# [sp+0x20] (sp of caller)
0x00007fd335060aa0: mov %eax,-0x14000(%rsp)
0x00007fd335060aa7: push %rbp
0x00007fd335060aa8: sub $0x10,%rsp ;*synchronization entry
; - Foo::frobincate@-1 (line 4)
0x00007fd335060aac: mov 0x60(%r15),%rdi
0x00007fd335060ab0: mov %rdi,%r10
0x00007fd335060ab3: add $0xfb0,%r10
0x00007fd335060aba: cmp 0x70(%r15),%r10
0x00007fd335060abe: jae 0x00007fd335060b18
0x00007fd335060ac0: mov %r10,0x60(%r15)
0x00007fd335060ac4: prefetchnta 0xc0(%r10)
0x00007fd335060acc: movq $0x1,(%rdi)
0x00007fd335060ad3: prefetchnta 0x100(%r10)
0x00007fd335060adb: movl $0xe7780204,0x8(%rdi) ; {oop({type array int})}
0x00007fd335060ae2: prefetchnta 0x140(%r10)
0x00007fd335060aea: movl $0x3e8,0xc(%rdi)
0x00007fd335060af1: prefetchnta 0x180(%r10)
0x00007fd335060af9: add $0x10,%rdi
0x00007fd335060afd: mov $0x1f4,%ecx
0x00007fd335060b02: xor %rax,%rax
0x00007fd335060b05: shl $0x3,%rcx
0x00007fd335060b09: rex.W rep stos %al,%es:(%rdi)
0x00007fd335060b0c: add $0x10,%rsp
0x00007fd335060b10: pop %rbp
0x00007fd335060b11: test %eax,0xbb954e9(%rip) # 0x00007fd340bf6000
; {poll_return}
0x00007fd335060b17: retq
0x00007fd335060b18: mov $0x3e8,%edx
0x00007fd335060b1d: mov $0x73bc01020,%rsi ; {oop({type array int})}
0x00007fd335060b27: callq 0x00007fd33505f260 ; OopMap{off=140}
;*newarray
; - Foo::frobincate@3 (line 4)
; {runtime_call}
0x00007fd335060b2c: jmp 0x00007fd335060b0c ;*newarray
; - Foo::frobincate@3 (line 4)
0x00007fd335060b2e: mov %rax,%rsi
0x00007fd335060b31: add $0x10,%rsp
0x00007fd335060b35: pop %rbp
0x00007fd335060b36: jmpq 0x00007fd335062160 ; {runtime_call}
0x00007fd335060b3b: hlt
0x00007fd335060b3c: hlt
0x00007fd335060b3d: hlt
0x00007fd335060b3e: hlt
0x00007fd335060b3f: hlt
[Exception Handler]
[Stub Code]
0x00007fd335060b40: jmpq 0x00007fd33505f3e0 ; {no_reloc}
[Deopt Handler Code]
0x00007fd335060b45: callq 0x00007fd335060b4a
0x00007fd335060b4a: subq $0x5,(%rsp)
0x00007fd335060b4f: jmpq 0x00007fd335038f00 ; {runtime_call}
0x00007fd335060b54: hlt
0x00007fd335060b55: hlt
0x00007fd335060b56: hlt
0x00007fd335060b57: hlt
It's an obvious optimization though, and it depends on the compiler/jvm/architecture/platform. So it's probably safer to parameterize the buffer for the benchmark. Another option would be making the buffer a public
static
member, or store a reference as a public
static
member. It increases the scope beyond something the compiler can effectively optimize.
Upvotes: 2
Reputation: 46482
I can't answer the main question, but I can offer a remedy:
the easiest way to remove the effect of the memory allocation would be to start the method by creating an unused array
Just "use" the array. In case you're using JMH, assign it to a blackhole. If you're using Caliper, then do something like result += myArray.hashCode()
.
Looking at the great lengths, JMH goes to avoid the risk of dead code elimination, I would never write a benchmark allowing any dead code. It may or may not work and it may break anytime.
Upvotes: 2