|
The Java Specialists' Newsletter
Issue 046 2002-05-05
Category:
Software Engineering
Java version: "The compiler team is writing useless code again ..."by Dr. Heinz M. Kabutz
Welcome to the 46th edition of The Java(tm) Specialists'
Newsletter, read in 82 countries,
with newest additions of Guatemala and Bharain. After spending
two months in Germany consulting on a project to get a 3-tier
architectured program running faster (a lot faster), I am safely
back in my own home in Somerset West, South Africa. Amazingly,
I'm actually sticking to the speed limit, possibly because my
Alfa makes as much noise at 100km/h as my uncle's Audi A6 made
at 180km/h.
This newsletter has now been published in electronic book form
(http://www.javafaq.nu/java/500/index.shtml)
along with hundreds of tips on how to do complicated things in
Java. The electronic book was put together by the author of
JavaTips and
is great for learning how to do clever things with Java. Please
note that you do not need to get the electronic book to access my
newsletters, they are still all available on my homepage.
Would you like to really understand Java concurrency? Join us for an
in-depth study of how threading works in Java. During the course,
you will learn how to write correct and fast multi-threaded Java code.
Please
click here if you would like to learn more. "The compiler team is writing useless code again ..."
During the two months in Germany, I learned a lot of new tricks
for solving performance related problems, and they will be making
great newsletter topics over the next few weeks, so stay tuned.
This newsletter is actually about how much confidence Sun's
JDK developers have in the work of their compiler developers'
abilities.
Cloning vs. Serialization
One of the performance improvements that we did was write a cache
for our business objects sitting in the middleware. It was meant
to be a read-only cache, so we actually wanted to copy the
business objects when we put them into the cache.
Since we wanted our framework to be easy for application
developers to use, our first attempt was to avoid using the
clone() method [clone does a shallow copy, and
writing a correct deep clone() method is quite challenging].
Instead, we simply serialized the objects into the cache, and
then deserialized them again on access. Our gut feeling was that
this was a bad idea due to the performance problems with
serialization, and after some tests we found out that cloning was
about ten times faster then copying via the serialization
mechanism.
A venture into unread(able) code
I was curious whether the serialization mechanism employed some
clever tricks that I didn't know about, so I started digging a
bit.
I wonder how many have read the serialization code, specifically
java.io.ObjectStreamClass? If you have, how far did
you read? Right until the end?
Right at the bottom of the class java.io.ObjectStreamClass in the
JDK 1.3.1 is the following code (this gem was removed in
JDK 1.4):
private final static Class[] NULL_ARGS = {};
//WORKAROUND compiler bug with following code.
//static final Class[] OIS_ARGS = {ObjectInpuStream.class};
//static final Class[] OOS_ARGS = {ObjectOutpuStream.class};
private static Class[] OIS_ARGS = null;
private static Class[] OOS_ARGS = null;
private static void initStaticMethodArgs() {
OOS_ARGS = new Class[1];
OOS_ARGS[0] = ObjectOutputStream.class;
OIS_ARGS = new Class[1];
OIS_ARGS[0] = ObjectInputStream.class;
}
}
Higher up in the class we see where OIS_ARGS is used:
/* Cache lookup of writeObject and readObject for
* Serializable classes. (Do not lookup for
* Externalizable)
*/
if (serializable && !forProxyClass) {
/* Work around compiler bug. See declaration for
* more detail.
*/
if (OOS_ARGS == null || OIS_ARGS == null) {
initStaticMethodArgs();
}
writeObjectMethod =
getDeclaredMethod("writeObject", OOS_ARGS,
Modifier.PRIVATE, Modifier.STATIC);
if (writeObjectMethod != null) {
hasWriteObjectMethod = true;
}
readObjectMethod =
getDeclaredMethod("readObject", OIS_ARGS,
Modifier.PRIVATE, Modifier.STATIC);
}
What a strange piece of code? Why did the programmer write it
like that? The reason for caching OIS_ARGS is obvious. It seems
wasteful to construct a new class array each time you want to
find the methods writeObject and
readObject.
But what's this story about a compiler bug?
We all make mistakes
Let's look more carefully at the declaration:
//WORKAROUND compiler bug with following code.
//static final Class[] OIS_ARGS = {ObjectInpuStream.class};
//static final Class[] OOS_ARGS = {ObjectOutpuStream.class};
I can picture the poor programmer tasked with producing the
serialization mechanism in record time, typing
ObjectInpuStream.class instead of
ObjectInputStream.class then trying to
compile his code, resulting in a message such as:
symbol : class ObjectInpuStream
location: class ObjectStreamClass
static final Class[] OIS_ARGS = {ObjectInpuStream.class};
My first reaction would've been: "Oh no, those silly compiler guys
cannot handle a circular dependency of classes within one
package." I would then have had a coffee to destress and
would've told all my colleagues that the compiler team were a
bunch of boneheads who should've listened more carefully in their
Compilers 101 class at university. I would also have realised
that it was futile to try and convince "them" that they had made
a mistake.
That was probably also the initial reaction of the Sun coder.
My second reaction would've been to initialize the static data
members "lazily", that is, to write an init() method that is
called at the first use. With that, I would've lost the "final"
modifier on the data members.
That seemed to also have been the second reaction of the Sun coder.
My final reaction would've been to liberally sprinkle comments
in my code to tell future readers that the compiler programmers
are uncooperative boneheads who should rather be manually testing
GUIs than writing compilers for a complicated language such as
Java.
For some reason, I think that was also how our coder reacted.
I do not know how long it would have taken me to spot this simple
spelling mistake. I know that if I counted all the hours that
such small mistakes wasted, it would amount to quite a long time.
Lessons Learnt
Four eyes are better than two: Peer programming would have
found this problem much quicker, and the programmer would not
have been as easily tempted to churn out such code.
We all make mistakes: It is possible to write correct code
that is poorly designed and inefficient. When something is slow
or does not work, could it possibly be my fault?
Sun's JDK developers don't trust the ability of their compiler
team: Should we?
Until the next issue...
Heinz
Software Engineering Articles
Related Java Course
Discuss at The Java Specialist Club
|