chiastic-security
chiastic-security

Reputation: 20520

Unused array of primitives: what do javac and the JIT compiler do with it?

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

Answers (2)

Jason
Jason

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

maaartinus
maaartinus

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

Related Questions