# Thursday, 17 April 2008
Invalid Casting Goodness

Yesterday Miguel blogged about a nice new feature in Mono. I added the IKVM_VERBOSE_CAST environment variable to IKVM to do something similar a while ago.

public class test {
  public static void main(String[] args) {
    System.out.println((String)(Object)args);
  }
}

C:\j>\ikvm-0.36.0.11\bin\ikvm test
Exception in thread "main" java.lang.ClassCastException
        at test.main(test.java)

C:\j>set IKVM_VERBOSE_CAST=1

C:\j>\ikvm-0.36.0.11\bin\ikvm test
Exception in thread "main" java.lang.ClassCastException: Object of type "System.String[], mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" cannot be cast to "System.String, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
        at test.main(test.java)

Note that the assembly qualified type names are displayed, as I believe this feature is particularly useful when trying to debug issues that arise from having loaded multiple assemblies that contain the "same" types.

While writing this I discovered that both JDK 1.6 and .NET 2.0 always generate descriptive exception messages for invalid casts:

C:\j>\jdk1.6\bin\java test
Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.String; can not be cast to java.lang.String
        at test.main(test.java:5)

C:\j>\ikvm\bin\ikvm test
Exception in thread "main" java.lang.ClassCastException: Unable to cast object of type 'System.String[]' to type 'System.String'.
        at test.main(test.java:5)

This last result is my local ikvm development version running on .NET 2.0 with a patch to enable taking the exception message from the .NET InvalidCastException, which I didn't previously do because on .NET 1.1 this message didn't contain any useful information.

Thursday, 17 April 2008 08:51:38 (W. Europe Daylight Time, UTC+02:00)  #    Comments [2]
# Monday, 14 April 2008
New Development Snapshot

It's been quite a while since the last development snapshot. I'm still working on integrating .NET 2.0 features, but I'm also doing random fixes/improvements here and there as I come across them. The biggest visible change in this snapshot is the support for defining .NET properties as Java fields. The main motivation was that I wanted to handle the System.in/out/err fields more cleanly. They are now implemented like this:

@ikvm.lang.Property(get="get_in")
public final static InputStream in;

static { in = null; }

private static InputStream get_in()
{
    return StdIO.in;
}

This defines a .NET property called "in" and associates the getter method of the property with the get_in() method. Note that we're only specifying the getter here in the Property annotation because the field is final, but you can also specify a setter method. The static initializer that initializes the property to null is necessary to keep javac happy, but it doesn't actually do anything. The ikvm bytecode compiler will ignore any assignments to read-only properties. Another thing to note is that the get_in() will automatically be made public (because the field is public), but from Java it will still appear private.

Changes since previous development snapshot:

  • Fixed regression in return value annotation value.
  • Forked Class, Constructor and Field.
  • Made class annotation handling lazy and bypass encode/decode.
  • Fixed ReflectionOnly referenced assembly loading order (ikvmstub).
  • Initialize class library in JVM_CreateJavaVM.
  • Reintroduced guard against recursive FinishCore invocations.
  • Implemented support for annotations on .NET fields/methods/parameters.
  • Hide ikvmc generated GetEnumerator() method from Java.
  • Simplified annotation handling.
  • Added support to Class.forName() for assembly qualified Java type names.
  • Replaced notion of DynamicOnly types with Fake types. Fake types are implemented as generic type instances and can have DynamicOnly methods.
  • Changed System.nanoTime() implementation to use Stopwatch.GetTimestamp().
  • Ripped out annotation/constant pool support that is no longer needed.
  • Added support for defining unloadable (i.e. missing) types to use as custom modifiers in signatures.
  • Use custom modifiers to make sure constructor signature is unique (if necessary).
  • Restructured code to remove compiler warnings.
  • Updated FlushFileBuffers p/invoke to use SafeFileHandle.
  • Forked OpenJDK sources that are going to be modified to refactor the library initialization.
  • Made __Fields nested class abstract (it was already sealed) and removed the constructor.
  • Restored the special case for interface .cctor methods to fix bug #1930303
  • Added ikvm/internal/MonoUtils.java. A new helper class to contain Mono specific methods.
  • Added Mac OS X platform detection.
  • Fixed System.mapLibraryName() to use platform detection instead of os.name property.
  • Improved java.library.path for Windows, Linux and Mac OS X.
  • Added support for setting os.name and os.ver on Mac OS X.
  • Try to guess os.arch based on IntPtr.Size.
  • Set sun.nio.MaxDirectMemorySize to -1 to allow "unlimited" direct byte buffers.
  • Fixed memory mapped file bug that caused mapping at non-zero file position to fail.
  • Close mapping handle using the Close() method on SafeFileHanlde instead of p/invoking the Win32 API directly.
  • Added support for filenames/paths with colons in them to Win32FileSystem.CanonicalizePath().
  • Added support for turning Java fields into .NET properties with an annotation.
  • Implemented System.in/out/err as .NET properties (explicitly).

WARNING: THIS IS A DEVELOPMENT SNAPSHOT, NOT AN OFFICIAL RELEASE.

Development snapshots are intended for evaluating and keeping track of where the project is going, not for production usage. The binaries have not been extensively tested and are not strong named.

If you want to run this version on Mono, you'll need a Mono version built from recent svn, it does not work on Mono 1.9.

Binaries available here: ikvmbin-0.37.3026.zip

Monday, 14 April 2008 07:40:06 (W. Europe Daylight Time, UTC+02:00)  #    Comments [0]
# Monday, 07 April 2008
IKVM 0.36 Update 1 Released

When I released IKVM 0.36 I said I intended to support 0.36 for a longer period since it is the last version that runs on .NET 1.1. Today I've released the first update release of 0.36 to SourceForge. For those who have been tracking the release candidates, this release is identical to release candidate 5.

Changes since IKVM 0.36.0.5:

  • Changed version to IKVM 0.36.0.11
  • Fix for reflection bug on .NET generic nested types that caused this.
  • Fix for bug #1865922.
  • java.awt.image.Raster fix.
  • Fix bug in DynamicMethod based serialization for fields typed as ghost interfaces.
  • Fixed ikvmc to support referencing assemblies that contain .NET type named java.lang.Object.
  • Improved error handling for ikvmc -reference option.
  • Optimized codegen for lcmp, fcmp, dcmp and shift opcodes.
  • Added support to Class.forName() for loading Java types with assembly qualified type names.
  • Implemented field/method/parameter annotation support for .NET types.
  • Added workaround for .NET 1.1 bug in Directory.CreateDirectory(). (bug #1902154)
  • Added -removeassertions optimization option to ikvmc.
  • Added -removeassertions to IKVM.OpenJDK.ClassLibrary.dll build.
  • Fixed JVM_CreateJavaVM to initialize the class library.
  • Fixed ikvmc to include zero length resource files.
  • Implemented SocketOptions.IP_MULTICAST_IF and SocketOptions.IP_MULTICAST_IF2.
  • Fixed assembly class loader to ignore codebase for dynamic assemblies (previously it would throw an exception).
  • Fixed exception stack trace code to return the .NET name of a type when a method in a primitive type is on the stack.
  • Fixed JNI reflection to filter out HideFromReflection members.
  • Fixed java.net.NetworkInterface to work on pre-Win2K3 systems.
  • Fixed java.lang.Thread to set context class loader for threads started from .NET.

If you want to build from source, you need ikvm-0.36.11.zip, classpath-0.95-stripped.zip, openjdk-b13-stripped.zip and the java.awt.image.Raster fix.

Monday, 07 April 2008 06:15:18 (W. Europe Daylight Time, UTC+02:00)  #    Comments [0]
# Monday, 24 March 2008
Generic Algorithms Revisited (Again)

Back in 2003 I described how you could theoretically use value types to efficiently inject behavior into a generic type. Recently I was thinking about this again and decided to look at the code generated by the JIT.

Let's look at a very simple case:

interface IOperation
{
  void DoIt();
}

struct Dummy : IOperation
{
  public void DoIt() { }
}

public class Program
{
  static void Gen<T>() where T : IOperation
  {
    default(T).DoIt();
  }

  static void Main()
  {
    for (; ; )
    {
      Gen<Dummy>();
    }
  }
}

The .NET 2.0 SP1 x86 JIT generates the following code for the Gen<Dummy>() method:

sub    esp,8
xor    eax,eax
mov    dword ptr [esp],eax
mov    dword ptr [esp+4],eax
mov    byte ptr [esp],0
movsx  eax,byte ptr [esp]
mov    byte ptr [esp+4],al
add    esp,8
ret

The good news is that the Dummy.DoIt() method has been inlined. The bad news is that every single instruction apart from the ret is unnecessary.

In order to understand what is going on, we need to look at the IL generated by the C# compiler for the Gen<T>() method and we also need to know that the CLR treats zero length types as having a length of one byte.

Here's the IL for Gen<T>():

.maxstack 1
.locals init ([0] !!T CS$0$0000, [1] !!T CS$0$0001)
ldloca.s CS$0$0000
initobj !!T
ldloc.0
stloc.1
ldloca.s CS$0$0001
constrained. !!T
callvirt instance void IOperation::DoIt()
ret

For some reason the default(T).Doit() construct results in two local variables being created. Now the JIT code above is starting to make a little sense (although it still isn't an excuse why it isn't optimized away):

sub    esp,8
xor    eax,eax
mov    dword ptr [esp],eax
mov    dword ptr [esp+4],eax
.locals init ([0] !!T CS$0$0000, [1] !!T CS$0$0001)
Reserve and initialize space on the stack for the two local variables. Note that they are both unnecessarily padded, so we need 8 bytes intsead of 4.
 
mov    byte ptr [esp],0 ldloca.s CS$0$0000
initobj !!T
Initialize the first local variable.
 
movsx  eax,byte ptr [esp] ldloc.0
Read the value of the first local variable.
 
mov    byte ptr [esp+4],al stloc.1
Store the value we just read in the second local variable.
 
  ldloca.s CS$0$0001
constrained. !!T
callvirt instance void IOperation::DoIt()
Inlined (empty) DoIt method.
 
add    esp,8
ret
ret
Unreserve stack space and return to caller.


We can improve the code a little by changing the Gen<T>() method body as follows:

T t = default(T);
t.DoIt();

Now the C# compiler won't generate the extra local variable and the x86 code looks a little better:

push   eax
xor    eax,eax
mov    dword ptr [esp],eax
mov    byte ptr [esp],0
pop    ecx
ret

For completeness here's the x64 code for the original Gen<Dummy>() method:

sub    rsp,38h
xor    eax,eax
mov    byte ptr [rsp+21h],al
xor    eax,eax
mov    byte ptr [rsp+20h],al
xor    eax,eax
mov    byte ptr [rsp+21h],al
movzx  eax,byte ptr [rsp+21h]
mov    byte ptr [rsp+20h],al
lea    rcx,[rsp+20h]
mov    rax,6428001C078h
call   rax
nop
add    rsp,38h
rep ret

This is even worse than the x86 code, because the Dummy.DoIt() method isn't even inlined.

NGEN

The standard excuse of why a JIT doesn't do some "obvious" optimizations is of course that it runs while your program is supposed to be running, so it can't take a lot of time to analyze and optimize the code. This is true to some extent (and JIT performance is one reason why running your managed code on x64 takes so much longer to start up), but it doesn't apply to NGEN and from what I understand today's NGEN doesn't do any more optimizations than the JIT compiler. I tested this specific example (for x64) and the NGEN code is indeed identical to the JIT code. This is unfortunate and hopefully, someday we'll see more sophisticated optimizations specific to NGEN as well.

Conclusion

This trick of using value types in generic algorithms appears to work reasonably well on x86, but there is still room for improvement in the JIT. Rumour has it that in the next .NET 2.0 service pack (due out this summer?) there will be an update of the JIT that finally supports inlining of methods that have value type arguments, let's hope that this JIT update will also include other optimizations as well. Let's also hope that this update includes a better version of the x64 JIT, because as this example shows yet again the x64 JIT still considerably lags behind the x86 JIT.

P.S. Here's the Mono 1.9 x86 JIT code for the original Gen<Dummy>():

push   ebp
mov    ebp,esp
sub    esp,8
mov    byte ptr [ebp-8],0
mov    byte ptr [ebp-4],0
mov    byte ptr [ebp-8],0
movsx  eax,byte ptr [ebp-8]
mov    byte ptr [ebp-4],al
lea    eax,[ebp-4]
push   eax
call   02A20278
pop    ecx
leave
ret

Monday, 24 March 2008 09:23:29 (W. Europe Standard Time, UTC+01:00)  #    Comments [2]
# Friday, 14 March 2008
IKVM 0.36 Update 1 Release Candidate 5

More fixes. Since I used version 0.36.0.10 for some interim fixes, this rc is 0.36.0.11.

Changes:

  • Changed version to 0.36.0.11.
  • Fixed ikvmc to include zero length resource files.
  • Implemented SocketOptions.IP_MULTICAST_IF and SocketOptions.IP_MULTICAST_IF2.
  • Fixed assembly class loader to ignore codebase for dynamic assemblies (previously it would throw an exception).
  • Fixed exception stack trace code to return the .NET name of a type when a method in a primitive type is on the stack.
  • Fixed JNI reflection to filter out HideFromReflection members.
  • Fixed java.net.NetworkInterface to work on pre-Win2K3 systems.
  • Fixed java.lang.Thread to set context class loader for threads started from .NET.
  • Added workaround for .NET 1.1 JIT bug exposed by bytecode optimizations introduced in 0.36.0.9.

Binaries available here: ikvmbin-0.36.0.11.zip
Sources (+ binaries):ikvm-0.36.0.11.zip

Friday, 14 March 2008 09:05:21 (W. Europe Standard Time, UTC+01:00)  #    Comments [0]
# Thursday, 28 February 2008
IKVM 0.36 Update 1 Release Candidate 4

More fixes and a couple of optimizations. The bytecode optimizations and the removal of assert statements resulted in IKVM.OpenJDK.ClassLibrary.dll actually becoming smaller for once:

Version IKVM.OpenJDK.ClassLibrary.dll size
0.36.0.5         26,808,320
0.36.0.9 26,697,728


The size difference is about evenly split between the bytecode optimizations and the assert statement removal.

The ikvmc option to remove assert statements is a bit of a cheat, but it turns out that in at least one case (java.util.BitSet) the assertions significantly affect performance (even when they are disabled) on .NET. This is the result of the CLR JIT not optimizing them away whereas HotSpot completely removes them when assertions aren't enabled. Given that this was affecting a real world scenario for an ikvm user I decided to add this option and compile the core class library with it. The option will only remove assert statements that it recognizes (i.e. the example code pattern mentioned in appendix IV of the assert spec).

Changes:

  • Changed version to 0.36.0.9.
  • Optimized codegen for lcmp, fcmp<x>, dcmp<x> and shift opcodes.
  • Added support to Class.forName() for loading Java types with assembly qualified type names.
  • Implemented field/method/parameter annotation support for .NET types.
  • Added workaround for .NET 1.1 bug in Directory.CreateDirectory(). (bug #1902154)
  • Added -removeassertions optimization option to ikvmc.
  • Added -removeassertions to IKVM.OpenJDK.ClassLibrary.dll build.
  • Fixed JVM_CreateJavaVM to initialize the class library.

Binaries available here: ikvmbin-0.36.0.9.zip
Sources (+ binaries):ikvm-0.36.0.9.zip

Thursday, 28 February 2008 08:01:31 (W. Europe Standard Time, UTC+01:00)  #    Comments [4]
# Tuesday, 19 February 2008
New Development Snapshot

More changes triggered by the changeover to .NET 2.0.I also did some C# 3.0 work which means that you can now do this:

using System;
using System.Collections;
using ikvm.extensions;
using java.util;

public class Program
{
  static void Main(string[] args)
  {
    try
    {
      var map = new HashMap {
        { "foo", args.getClass() },
        { "bar", 2.getClass() }
      };

      foreach (DictionaryEntry de in map)
        Console.WriteLine("{0} = {1}", de.Key, de.Value);
    }
    catch (System.Exception x)
    {
      x.printStackTrace();
    }
  }
}

BTW, to enable defining extension methods without taking a dependency on System.Core.dll, I've defined my own copy of System.Runtime.CompilerServices.ExtensionAttribute in IKVM.Runtime.dll (it's only public during the first compilation pass, so it does not interfere with the real one in System.Core.dll if your project references both IKVM.Runtime.dll and System.Core.dll).

Changes:

  • Renamed GetEnumerator method in implicit IEnumerable implementation to work around lame bug in Xml serialization.
  • Added support for defining non-virtual instance methods in map.xml.
  • Added "Add" method to java.util.AbstractCollection to make some of the .NET magic work on Java collections.
  • Reintroduced baked TypeBuilder scrubbing hack for .NET 2.0 SP1.
  • Made core assembly detection more robust.
  • Improved ikvmc error handling for -reference option.
  • Added support for defining extension methods in the core class library assembly (without taking a System.Core.dll dependency).
  • Added the first two extension methods (Object.getClass() and Exception.printStackTrace()).
  • Optimized codegen for lcmp, fcmp<x>, dcmp<x> and shift opcodes.
  • Construct custom attribute annotation proxies directly instead of going through the trouble to encode/decode them.
  • Added support for explicitly implementing an interface method with the <override /> element in map.xml.
  • Added new utility class to enumerate maps.
  • Made java.util.AbstractMap enumerable and added Add() method to support C# 3.0 collection initialization syntax.

WARNING: THIS IS A DEVELOPMENT SNAPSHOT, NOT AN OFFICIAL RELEASE.

Development snapshots are intended for evaluating and keeping track of where the project is going, not for production usage. The binaries have not been extensively tested and are not strong named.

This version does not currently work with Mono.

Binaries available here: ikvmbin-0.37.2970.zip

Tuesday, 19 February 2008 06:13:20 (W. Europe Standard Time, UTC+01:00)  #    Comments [0]
# Monday, 04 February 2008
IKVM 0.36 Update 1 Release Candidate 3

I screwed up the previous release candidate build by building it on .NET .2.0. So here's a new release candidate that's built on .NET 1.1 again (and I threw in a couple of small fixes as well.)

Changes:

  • Changed version to 0.36.0.8.
  • Fixed ikvmc to support referencing assemblies that contain .NET type named java.lang.Object.
  • Improved error handling for ikvmc -reference option.

Binaries available here: ikvmbin-0.36.0.8.zip
Sources (+ binaries):ikvm-0.36.0.8.zip

Monday, 04 February 2008 16:18:04 (W. Europe Standard Time, UTC+01:00)  #    Comments [0]
# Thursday, 31 January 2008
How to Disassemble an AtomicReferenceFieldUpdater

It's been a while since I've done an in depth investigation of a microbenchmark and the recent work I did on AtomicReferenceFieldUpdater to make it work in partial trust also has a nice performance impact. So let's investigate that.

The Microbenchmark

import java.util.concurrent.atomic.*;

class Test {
  volatile Object field;

  public static void main(String[] args) {
    AtomicReferenceFieldUpdater upd =
      AtomicReferenceFieldUpdater.newUpdater(Test.class, Object.class, "field");
    Test obj = new Test();
    for (int j = 0; j < 5; j++) {
      long start = System.currentTimeMillis();
      for (int i = 0; i < 10000000; i++)
        upd.compareAndSet(obj, null, null);
      long end = System.currentTimeMillis();
      System.out.println(end - start);
    }
  }
}

The Results

       IKVM 0.34      IKVM 0.36      IKVM 0.37      JDK 1.6
.NET 1.1 36808 41453    
.NET 2.0 / x86       75647 5776 561 321
.NET 2.0 / x64   5081 512 245

The Differences

The first thing that jumps out is that the IKVM 0.34 results show that .NET 2.0 reflection is much slower than .NET 1.1 reflection. On IKVM 0.36 the reflection implementation changed to take advantage of DynamicMethod when running on .NET 2.0, so there we see a big improvement in performance when running on .NET 2.0.

IKVM 0.37 has the new AtomicReferenceFieldUpdate optimization that no longer uses reflection (if it can figure out at compile time what to do), this again yields a big performance improvement.

Finally, HotSpot manages to beat IKVM be a factor of two. There is no difference between HotSpot client and server modes for this benchmark (on JDK 1.6).

The Compiler

Let's look at some C# pseudo code that shows what ikvmc 0.37 generates for the above benchmark:

using java.util.concurrent.atomic;
using System.Threading;

class Test {
  volatile object field;

  private sealed class __ARFU_fieldLjava/lang/Object; : AtomicReferenceFieldUpdater {
    public override bool compareAndSet(object obj, object expect, object update) {
      return expect == Interlocked.CompareExchange(ref ((Test)obj).field,
                                                   (object)update, (object)expect);
    }
    // ...other methods omitted...
  }

  static void main(string[] args) {
    AtomicReferenceFieldUpdater udp = new __ARFU_fieldLjava/lang/Object;();
    // ...rest of method omitted...
  }
}

The bytecode compiler only does this optimization if the arguments to newUpdater are constants and match up with a volatile instance reference field in the current class.

The reason this optimization only first showed up in IKVM 0.37 is that it requires the generic version of Interlocked.CompareExchange. In this particular example the non-generic version would have worked, but in the real world nearly all uses of AtomicReferenceFieldUpdater are on fields that have a more specific type than Object.

The Assembly

So why is HotSpot twice as fast? I modified the test slightly to make the generated assembly code easier to read by making it an infinite loop. Here's the x64 code for the loop:

00000000028C2690   mov          r11,qword ptr [r8+10h]
00000000028C2694   mov          r10,1026DD08h
00000000028C269E   cmp          r11,r10
00000000028C26A1   jne          00000000028C2773
00000000028C26A7   mov          r10,qword ptr [r8+20h]
00000000028C26AB   test         r10,r10
00000000028C26AE   jne          00000000028C273B
00000000028C26B4   mov          r10,qword ptr [r8+28h]
00000000028C26B8   mov          r11,r9
00000000028C26BB   add          r11,r10
00000000028C26BE   xor          eax,eax
00000000028C26C0   xor          r10d,r10d
00000000028C26C3   lock cmpxchg qword ptr [r11],r10
00000000028C26C8   sete         r12b
00000000028C26CC   movzx        r12d,r12b
00000000028C26D0   mov          r10,r11
00000000028C26D3   shr          r10,9
00000000028C26D7   mov          r11,589FF80h
00000000028C26E1   mov          byte ptr [r11+r10],0
00000000028C26E6   test         dword ptr [160000h],eax
00000000028C26EC   jmp          00000000028C2690

HotSpot did it's thing and was able to inline the virtual compareAndSet method. I'm pretty sure that HotSpot doesn't have special support for AtomicReferenceFieldUpdater, but this is simply the normal HotSpot devirtualization optimization at work. The lock cmpxchg instruction is the result of HotSpot having intrinsic support for sun.misc.Unsafe.compareAndSwapObject.

Let's go over the assembly instructions in detail:

00000000028C2690   mov          r11,qword ptr [r8+10h]
00000000028C2694   mov          r10,1026DD08h
00000000028C269E   cmp          r11,r10
00000000028C26A1   jne          00000000028C2773

This looks like a HotSpot virtual method inline guard. It's checking to make sure that the object is of the expected type (if it isn't, the inlined virtual method may not be correct anymore).

00000000028C26A7   mov          r10,qword ptr [r8+20h]
00000000028C26AB   test         r10,r10
00000000028C26AE   jne          00000000028C273B

I'm not sure. Some field in the AtomicReferenceFieldUpdater object is tested for null.

00000000028C26B4   mov          r10,qword ptr [r8+28h]

The offset to the field is loaded from the AtomicReferenceFieldUpdater object.

00000000028C26B8   mov          r11,r9

The passed in object reference is moved from r9 to r11.

00000000028C26BB   add          r11,r10

Add the field offset to the object reference. We now have the address of the memory location we want to update in r11.

00000000028C26BE   xor          eax,eax

Clear rax to represent the passed in null value of the expect argument. I'm not sure why the disassembler shows the register as eax, but this instruction clears the full 64 bit rax register.

00000000028C26C0   xor          r10d,r10d

r10 is cleared and represents the passed in null value of the update argument.

00000000028C26C3   lock cmpxchg qword ptr [r11],r10

The actual interlocked compare and exchange instruction. The qword at memory location r11 is compared with rax and if it matches r10 is written to it. Since I'm on a dual core machine, the lock prefix is applied. Locking the bus is expensive, so HotSpot omits it when running on a single core machine.

00000000028C26C8   sete         r12b
00000000028C26CC   movzx        r12d,r12b

The cmpxchg instruction sets the zero flag if it was successful. These two instruction copy the zero flag into the r12 register (it is set to 0 or 255 to represent either false or true). Since the result isn't actually used in this case, this could have been optimized away.

00000000028C26D0   mov          r10,r11
00000000028C26D3   shr          r10,9
00000000028C26D7   mov          r11,589FF80h
00000000028C26E1   mov          byte ptr [r11+r10],0

This is a little interesting. It takes the address of the field that was just (potentially) updated and shifts it to the right by 9 bits and uses that value to index a static table and clear the corresponding byte. This is a GC write barrier. The GC consults the table (known as a card table) to know what objects in older generations it needs to scan when doing a GC of a younger generation.

00000000028C26E6   test         dword ptr [160000h],eax

This seemingly useless test is part of a mechanism used by the VM to suspend the thread at this instruction (a safepoint). When the VM wants to suspend all threads (for a GC) it unmaps the safepoint polling memory page (in this case at 0x160000) and waits for all threads to suspend. Each thread running compiled Java code will eventually run this instruction and cause a page fault, inside the page fault handler it is detected that a safepoint thread suspend is requested and the thread calls the VM to suspend itself.

00000000028C26EC   jmp          00000000028C2690

Branch to the top and start over again.

The Conclusion

The .NET Framework JIT doesn't inline virtual methods and Interlocked.CompareExchange is not a JIT instrisic, so there the story is pretty straightforward. Each loop iteration calls Interlocked.CompareExchange which in turn calls the GC write barrier function. This is why HotSpot is able to beat IKVM 0.37 by a factor of two.

Of course, when you're coding in C# you can write the microbenchmark to call Interlocked.CompareExchange directly:

using System;
using System.Threading;

class Test {
  volatile object field;

  static void Main(string[] args) {
    Test obj = new Test();
    for (int j = 0; j < 5; j++) {
      int start = Environment.TickCount;
      for (int i = 0; i < 10000000; i++)
        Interlocked.CompareExchange(ref obj.field, null, null);
      int end = Environment.TickCount;
      Console.WriteLine(end - start);
    }
  }
}

This runs in 265 milliseconds which goes to show that in this case all the fancy footwork that HotSpot does can almost be matched simply by having by ref argument passing in your language. Of course, the CLR JIT isn't perfect. When you change the field type to string the running time increases to 436 milliseconds because the invocation of a generic method goes through a stub that makes sure that the method instantiation exists. Here it would probably pay to to teach the JIT about the generic methods in System.Threading.Interlocked.

Thursday, 31 January 2008 10:50:35 (W. Europe Standard Time, UTC+01:00)  #    Comments [3]
# Friday, 25 January 2008
IKVM 0.36 Update 1 Release Candidate 2

One more bug fix.

Changes:

  • Changed version to 0.36.0.7.
  • Fix bug in DynamicMethod based serialization for fields typed as ghost interfaces.

Binaries available here: ikvmbin-0.36.0.7.zip
Sources (+ binaries):ikvm-0.36.0.7.zip

The external sources are the same as the previous rc.

Friday, 25 January 2008 07:38:42 (W. Europe Standard Time, UTC+01:00)  #    Comments [0]