Andy Malakov software blog
Thursday, August 14, 2008
Tuesday, August 5, 2008
Update on "direct vs. encapsulated field access times"
static void benchmark () throws Exception {
FieldAccessor fv = new FieldAccessor ();
MethodAssessor mv = new MethodAssessor ();
System.err.println ("\tTotal, Method, Field, Ratio");
for (int n=1, total = 0; n < 10000000; n=2*n) {
final long t0 = System.nanoTime();
for (int i = 0; i < n; i++) {
if ((mv.getField() & 0x101) != 0)
mv.setField(mv.getField()+1);
else
mv.setField(mv.getField()+2);
}
final long t1 = System.nanoTime();
for (int i = 0; i < n; i++) {
if ((fv.field & 0x101) != 0)
fv.field = (fv.field+1);
else
fv.field = (fv.field+2);
}
final long t2 = System.nanoTime();
// dispay stats
final double mvCycleTime = (double)(t1 - t0) / n;
final double fvCycleTime = (double)(t2 - t1) / n;
final double ratio = 100.0*fvCycleTime/mvCycleTime;
total +=n;
System.err.printf("% 12d, %.3f, %.3f, %.2f%% %c\n",
total, mvCycleTime, fvCycleTime, ratio,
(fv.field == mv.getField() ? ' ' : '!'));
Thread.sleep(500);
}
}
This code produces the following output (with -XX:+PrintCompilation):
Total, Method, Field, Ratio
1, 5866.000, 1759.000, 29.99%
3, 5463.000, 405.500, 7.42%
7, 1925.500, 217.750, 11.31%
15, 1166.000, 156.250, 13.40%
31, 648.313, 123.938, 19.12%
63, 440.438, 110.250, 25.03%
127, 401.922, 103.547, 25.76%
255, 270.688, 106.156, 39.22%
511, 243.715, 95.785, 39.30%
1023, 225.525, 94.367, 41.84%
2047, 231.510, 142.017, 61.34%
4095, 232.009, 97.745, 42.13%
2 tmp.TestFieldVsMethod$MethodAssessor::getField (5 bytes)
8191, 227.914, 97.593, 42.82%
3 tmp.TestFieldVsMethod$MethodAssessor::setField (6 bytes)
16383, 238.234, 98.486, 41.34%
1% tmp.TestFieldVsMethod::benchmark @ 44 (277 bytes)
32767, 13.628, 1.675, 12.29%
65535, 1.621, 1.666, 102.78%
131071, 1.611, 1.658, 102.88%
262143, 1.602, 1.655, 103.31%
524287, 1.599, 1.653, 103.41%
1048575, 1.897, 1.923, 101.40%
4 java.lang.String::charAt (33 bytes)
2097151, 1.606, 1.652, 102.88%
4194303, 1.595, 1.652, 103.52%
8388607, 1.595, 1.652, 103.53%
16777215, 1.595, 1.655, 103.76%
Notice how ratio dives down at around 30,000 mark:
What that probably means is that field access code is compiled faster:
Without knowing much about hotspot inner workings, I expected to see a sharper decline.
I run this test on server version of Java 6 (1.6.0_10-beta-b25, 64 bit).
Java NIO with large data files
It turns out that replacing that code with NIO's MappedByteBuffer processes 2 Gb file twice faster, while memory consumption remains the same.
Update: I have been warned and it turned out to be true: Reading 20 Gb file shows reverse results. Now NIO version is 8x slower than "old" stream-based approach. In this case I implemented paging because Java NIO cannot map file regions larger than 2Gb at once (Integer.MAX_VALUE).
On Windows XP 8K is a sweet spot for BufferedInputStream buffer size. Allocating larger buffer actually reduces performance significantly. May be size of mapped region has similar optimum.
Direct field vs. Method access times
1. It makes performance-critical code run faster.
False. There is no benefit of direct access in frequently executed code. Chart below compares access time of direct field access to getters and setters.
As you can see, after 20,000 calls there is no difference in performance. This threshold is controlled by -XX:CompileThreshold JVM argument. Core of the test:
final long t0 = System.nanoTime();
for (int i = 0; i < n; i++) {
if ((mv.getField() & 0x101) != 0)
mv.setField(mv.getField()+1);
else
mv.setField(mv.getField()+2);
}
final long t1 = System.nanoTime();
for (int i = 0; i < n; i++) {
if ((fv.field & 0x101) != 0)
fv.field = (fv.field+1);
else
fv.field = (fv.field+2);
}
final long t2 = System.nanoTime();
2. Direct field access reduces bureaucracy.
Read: I am too lazy to write getters and setters. Well, Eclipse and Netbeans have "Encapsulate Field" and "Generate getters and Setters" commands. And BTW, what none of free IDEs have is "Find where modified" search for class fields.
Having said that, I have no problems with public final fields :-)
Thursday, July 31, 2008
A bug in Sun's 64-bit hotspot compiler
public class FailingTest {
private final java.io.RandomAccessFile raf;
private long sequence;
public FailingTest() throws Exception {
java.io.File f = new java.io.File("test.dat");
f.delete();
raf = new java.io.RandomAccessFile(f, "rw");
f.deleteOnExit();
}
public void test(long timestamp) throws Exception {
// This condition never happens but is required to
// reproduce this bug, so is unused timestamp argument
if (timestamp < 0)
System.exit(0);
final long valueToStore = sequence++;
raf.seek(0);
raf.writeLong(valueToStore);
raf.seek(0);
final long valueRead = raf.readLong();
if (valueRead != valueToStore)
System.err.println("Error: Read value " +
valueRead + " != " + valueToStore);
}
public static void main (String [] args) throws Exception {
FailingTest t = new FailingTest();
for (int ii = 0; ii < 1000000; ii++)
t.test(ii);
}
}
On my machine this test fails with the following output (I run it with -server -XX:CompileThreshold=100):
Error: Read value 4826 != 4825
The test fails every time with different numbers, with any version of 64-bit Sun JDK 1.6 we tried. This problem is NOT reproducible under 32-bit version and NOT reproducible on BEA's JRockit 1.6 (64 bit). Just verified that problem remains on the latest JVM available: build 1.6.0_10-beta-b25, Java HotSpot(TM) 64-Bit Server VM (build 11.0-b12, mixed mode).
Monday, July 21, 2008
Debug mode penalty in .NET and Java
That is why I was amazed to see that C# Debug version of my test was 14.5 times slower compared to Release (compiled as /optimize+ /checked- /debug-). I could have imagined such difference for C++, but not for C# that also has just-in-time (JIT) optimization at run-time.
Monday, July 7, 2008
Thursday, June 12, 2008
Ant task for IKVMC
Submitted Ant task for IKVMC - ant-ikvmc. I found multiple FileSet elements in Ant more convenient in defining source classes than -recurse:mask command line argument provided by IKVMC.
Example of task:
<ikvmc target="library" out="${dotnet.out}/uhfclient.dll" home="${ikvm.home}" version="1.0.0.0" debug="true" verbose="true">
<reference path="${dotnet.out}/uhfc-3rd-party.dll"/>
<fileset dir="${classes}" includes="${uhfdll.resources}">
<exclude name="**/*.vpp"/>
<exclude name="**/*.txt"/>
</fileset>
<fileset dir="${jars}">
<include name="**/*.jar"/>
<exclude name="foo.jar"/>
</fileset>
</ikvmc>
Bonus: Java doclet that generates IKVMC mapping file. Among other things mapping file can specify parameter names in methods and constructors translated by IKVMC. Since Java parameter names are not available from Java class files, IKVMC needs this hint to preserve original Java names. This is handy if you generate API libraries that will be used by .NET developers.
Here is an example of javadoc task to generate IKVM mapping file for selected packages:
<javadoc sourcepath="${src}" classpath="${javac.classpath}" defaultexcludes="yes">
<doclet name="deltix.tools.ikvmc.ant.IkvmcMapDoclet" path="${classes}">
<param name="-out" value="${mapfile}"/>
</doclet>
<package name="com.acme.api"/>
<package name="com.acme.util"/>
</javadoc>
IKVM for Java - .NET interoperability
Front-end to our Java-based server is written on C#/.NET. Interoperability is achieved using XML/WS-based server API (as well as some custom binary format for high volume data responses). Typical architecture. Well, this is going to change now, after we discovered IKVM project. IKVM translates Java byte code into MSIL.
The lack of Microsoft Java implementation for .NET platform has obvious commercial reason. But what cannot be done by the largest software company was done by single developer - Jeroen Frijters (the man behind IKVM). Amazing.
JAXB 2.1 is now in Java 6 (almost)
Looks like Sun folks silently updated Java 6 to use JAXB 2.1 API. The fact that Java 6 was shipped with old version of JAXB used to be a lot of pain. Hopefully we don't need to mess with endorsed directory workaround anymore.
I run a diff and it turns out that JAXB API shipped with JDK 1.6 is almost identical to 2.1. It has only one significant difference from 2.1.6: Class javax.xml.bind.ContextFinder refers to a different location of Context Factory. In com.sun.xml.internal.bind.* became com.sun.xml.bind.*
Bottom line: if you want to use JAXB 2.1 with 1.6, simply add JAXB implementation in your classpath. No need to use Java endorsed mechanism anymore.
P.S. Also the following frequent error is now a history: "java.lang.LinkageError: JAXB 2.0 API is being loaded from the bootstrap classloader, but this RI requires 2.1". If you still see it with JAXB 2.1 in classpath simply update your Java 6 to the latest version (I tested it with 1.6.0_04-b12+ which was released in Spring 2008).
Blog Archive
- November 2024 (1)
- November 2016 (1)
- March 2015 (2)
- December 2011 (1)
- October 2010 (2)
- June 2010 (1)
- May 2010 (1)
- December 2009 (1)
- November 2009 (4)
- October 2009 (1)
- July 2009 (2)
- June 2009 (1)
- December 2008 (1)
- September 2008 (2)
- August 2008 (4)
- July 2008 (3)
- June 2008 (4)
- May 2008 (1)