Andy Malakov software blog

Wednesday, November 11, 2009

Stack allocation in Java is still a myth

There were rumors that Mustang will have on stack allocation as a part of hot spot optimization.

Four+ years later this :


public long encode (String input) {
final byte [] buffer = new byte [8];

.. encode input into buffer

.. convert buffer into LONG

return result;
}


I was hoping JVM will allocate buffer on stack based on the fact that it does not escape from this method. Running this test 10M times with -verbosegc shows extensive GC work (1.6.0_16-b01 64 bit server JVM with -XX:+DoEscapeAnalysis option).



On the positive side. GC is very fast. Consider three functions:

  1. encodeNewBuffer () uses new byte array to encode input string.

  2. encodeSynchronizedField () uses private field, guarded by synchronized{} block

  3. encodeThreadLocalField () uses ThreadLocal cache to encode input string



Here is the code:

long encodeNewBuffer (String input) {
final byte [] buffer = new byte [8];

return f (buffer);
}

/////////////

private final byte [] buffer = new byte [8];

synchronized long encodeSynchronizedField (String input) {
return f (buffer);
}

/////////////

ThreadLocal<byte[]> threadLocal = new ThreadLocal<byte[]>();
{
threadLocal.set(new byte [8]);
}

long encodeThreadLocalField (String input) {
byte [] buffer = threadLocal.get();
return f (buffer);
}


GC-based method is a winner:


encodeNewBuffer(): 4,108 sec.
encodeSynchronizedField(): 5,322 sec.
encodeThreadLocalField() : 5,411 sec

Monday, November 9, 2009

Multi-threaded testing with JUnit. Switch to ConcJUnit .

Our JUnit tests deal with components that use multiple threads. Unfortunately JUnit has no built-in support for this kind of testing.

Lets consider a simple thread that interprets commands from input queue. In this example it can only understand "exit" command. All other inputs trigger an error.

 

abstract class MainLoop extends Thread {

private Queue<String> commands = new LinkedBlockingDeque<String>();


public void run () {
while (true) {
String command = commands.poll();
if (command.equals ("exit"))
break;

else
reportError (
"Unknown command: " + command);
}
}

public void add (String command) {
commands.add(command);

}

abstract void reportError (String error);
}

public class MyTest {

@Test
public void test() {
MainLoop thread =
new MainLoop() {

void reportError(String error) {
Assert.fail (error);
}
};
thread.start();
thread.add (
"quit"); // sic! shoudl be "exit"

}
}



If we run the above test, JUnit completely ignores failure of "Commands Interpreter" thread (Although default uncaughtExceptionHandler prints exception to standard output):


[junit] Running test.MyTest
[junit] Exception in thread "Commands Interpreter": java.lang.AssertionError: Unknown command: quit
[junit] at org.junit.Assert.fail(Assert.java:91)
[junit] at test.MyTest$1.reportError(MyTest.java:36)
[junit] at test.MainLoop.run(MyTest.java:19)
[junit] Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 0.422 sec


As you can see JUnit detected 0 errors. JUnit has at least two problems when it comes to multi-threaded tests:

  1. JUnit only cares about errors in its own thread. It completely ignores exceptions or assertion failures in child threads.

  2. Left over child threads may produce side effect on subsequent tests. In some cases failures in child threads may happen after the main thread is finished.



We used various custom workarounds until it was clear that we need a generic solution for growing number of multi-threaded tests. Luckily we found an excellent tool - ConcJUnit (AKA ConcuTest) written by Mathias Ricken.

a) ConcJUnit is a drop-in replacement of JUnit. It includes standard JUnit classes and introduces own test runners. Just replace junit.jar by concutest.jar .

b) ConcJUnit detects both failures in child threads and leftover threads.

Switching to ConcJUnit produces expected result:


java.lang.AssertionError: Unknown command: quit
at org.junit.Assert.fail(Assert.java:91)
at test.MyTest$1.reportError(MyTest.java:36)
at test.MainLoop.run(MyTest.java:19)
at test.MainLoop.run(MyTest.java:19)


If you want to know more about ConcJUnit, read materials from introduction page. For example, the following presentation.

Thursday, November 5, 2009

GLOP

"Consider a program — any program; let us call it GLOP. We begin designing our program by imagining that we have a machine with a hardware instruction that will perform GLOP (an idea that is not beyond the realm of possibility with today's microprogrammable computers) Thus, to write program GLOP, we merely write the instruction
GLOP
and we are done! Not only are we finished, but we are relatively certain that the resulting program is correct — assuming that the hardware primitive GLOP performs correctly (and if it doesn't, we can always blame it on the vendor)."

Edward Yourdon "Techniques of program structure and design‎" 1975.

Tuesday, November 3, 2009

2:5020/236.23 :-)