Abstract: In this book, Jack outlines the process used to make Java systems run faster. He gives lots of tips on how to find your bottlenecks and then also gives specific tricks to make your code just that bit faster. A must-have for Java programmers who care about the speed of their programs.
Welcome to the 66th edition of The Java(tm) Specialists' Newsletter, sent to 6138 Java Experts in 94 countries. The newslist server is going to start throwing out dead email addresses, so the side effect is that the number of subscribers will likely decrease. If you did not receive this email, please respond by replying to this email and telling me that you did not receive it.
Last week I had great fun with some programmers in Frankfurt. We did the Design Patterns Course and good laughs together and of course we all learned new things. Thanks especially to my friend Carl Smotricz (who used to host our newsletter archive) for organising this event. One of the highlights of my trip was meeting with some of our newsletter subscribers for a good German beer.
During my recent travels, I had the opportunity to meet with Jack Shirazi, famous as the author of the landmark book Java Performance Tuning [ISBN 0596003773] . Besides consulting on performance related issues, Jack also maintains an excellent website, which is the #1 resource for expert Java programmers who want to keep up to date with Java performance. I particularly like his website because true to the topic, it is fast to navigate. The website does not look particularly pretty, but you can easily get to the information you want. There are no frames, no pictures, no special fonts, but gosh, does it render quickly! When I chatted to Jack about this, he told me that according to the principles in his book, the fastest content that you could possibly serve is static content, i.e. content that is not dynamically generated. Jack therefore generates his entire website and uploads the differences. This works because the content does not change all the time, it would be a bit difficult to build an entire internet banking site like this. However, even the internet banking site should be serving static content whenever this is possible.
Jack and I spent a good few hours chatting about all sorts of topics, from the state of consulting to business ideas, to how many kids we have (at this point I knew that Jack was hopelessly superior at a 3:1 ratio and we agreed to decide on greatness by means of an arm-wrestle). The waitress was quite amused since neither of us really had any idea of what we were doing. There are two types of people in this world: those that can program, and those that can arm-wrestle. Our inability to achieve neither victory nor defeat has placed us firmly into the camp of programmers :-)
Ah, here is a good job interview question: "Hi, so you want to work for the XYZ company where we build important software. What did you think of the {rugby|cricket|soccer|football|basketball|baseball} game on Saturday?" An answer like "Was there a match? Who played?" will get you the job as programmer without any further questions, but saying "Yeah, Ted performed well - he has recovered well since his knee operation that he had in high school - he went to Osborne High from 1984-1989 where he was Valedictorian, did you know?" will only get you a position in the sales department. Mind you, the sales department is not a bad place to be - at least you can watch sport on the weekend and tell your boss and spouse that you are doing important work.
Let us not get sidetracked...
javaspecialists.teachable.com: Please visit our new self-study course catalog to see how you can upskill your Java knowledge.
Naturally I did not just travel 10000km to speak to Jack about kids and lucrative consulting jobs. [To those of you who do not have kids, once you have them, you will realise that they are far more important than anything else in life, even Java.]
The real reason I met with Jack was to speak about the 2nd Edition of his book Java Performance Tuning [ISBN 0596003773] . When Jack wrote the first edition of his book, there was not much else available. The one book that I have mentioned in this newsletter is Java 2 Performance and Idiom Guide [ISBN 0130142603] , and that was available before Jack's. However, Jack's book focuses far more on performance than the other book. In addition, Jack adds value to the reader with his informative website that is worthwhile to explore.
The book contains chapters on the following performance topics:
The book starts with a chapter outlining a performance tuning strategy that you should follow when attempting to tune an application. Follow it, and you will be successful. Ignore it, and you will forever be tuning and never get any acknowledgements for your achievements. At the end of each chapter is a performance checklist of the major points of that chapter. For example:
Many moons ago, when Java had just been invented, programmers invented strange
ways to write optimal code. One of the weirdest abominations was the logic that
a try/catch
was for free when no exception was
thrown and that it was cheaper than if
and
instanceof
statements. For example:
public class TryCatch { public static boolean test1(Object o) { try { Integer i = (Integer)o; return false; } catch(Exception e) { return true; } } public static boolean test2(Object o) { if (o instanceof Integer) { Integer i = (Integer)o; return false; } else { return true; } } }
The thinking was that a try block was free, so therefore
if you almost always have Integer objects, it will be faster
to just cast and hope for the best, than to first test if
it really is an Integer. test1()
should therefore
be faster than test2()
most of the time, unless
we passed in an object that was not an Integer.
But is it really faster? Let us have a look:
public class TryCatchCostTest extends TryCatch { public static void main(String[] args) { Integer i = new Integer(3); Boolean b = new Boolean(true); long TEST_DURATION = 2 * 1000; boolean res; long stop; stop = TEST_DURATION + System.currentTimeMillis(); int test1_i = 0; // we do not want to test with every increment, otherwise the // call to System.currentTimeMillis() will dwarf the rest of // our calls. while(((test1_i % 1000) != 0) || (System.currentTimeMillis() < stop)) { test1_i++; res = test1(i); } System.out.println("test1(i) executed " + test1_i + " times"); stop = TEST_DURATION + System.currentTimeMillis(); int test1_b = 0; while(((test1_b % 1000) != 0) || (System.currentTimeMillis() < stop)) { test1_b++; res = test1(b); } System.out.println("test1(b) executed " + test1_b + " times"); System.out.println("test1(i) was " + (test1_i / test1_b) + " times faster"); stop = TEST_DURATION + System.currentTimeMillis(); int test2_i = 0; while(((test2_i % 1000) != 0) || (System.currentTimeMillis() < stop)) { test2_i++; res = test2(i); } System.out.println("test2(i) executed " + test2_i + " times"); stop = TEST_DURATION + System.currentTimeMillis(); int test2_b = 0; while(((test2_b % 1000) != 0) || (System.currentTimeMillis() < stop)) { test2_b++; res = test2(b); } System.out.println("test2(b) executed " + test2_b + " times"); } }
When I run this test on JDK 1.4.1_01 with my Pentium 4m 1.7GHz, I get the following values under client hotspot:
test1(i) executed 22700000 times test1(b) executed 126000 times test1(i) was 180 times faster test2(i) executed 52420000 times test2(b) executed 53015000 times
Oops, it appears that logic was incorrect! test2()
is about twice as fast as the best test1()
! How does
this look with the server hotspot compiler?
java -server TryCatchCostTest test1(i) executed 50310000 times test1(b) executed 43000 times test1(i) was 1170 times faster test2(i) executed 51915000 times test2(b) executed 51952000 times
The difference between test1()
and test2()
is not as pronounced as with the client hotspot, but test2()
is still marginally faster. However, the version of test1()
that causes the exception has become three times slower!
Under what circumstances did this idea proliferate? My suspicion is that
it used to be true for the interpreted version of Java, probably around
JDK 1.0. Have a look at what happens when we run this test with the -Xint
switch. It appears that test1(i)
is indeed faster!
java -Xint TryCatchCostTest test1(i) executed 19153000 times test1(b) executed 180000 times test1(i) was 106 times faster test2(i) executed 14522000 times test2(b) executed 18719000 times
The book [ISBN 0596003773] is great in that for all important statistics, it compares performance results for Sun JDK 1.1.8, 1.2.2, 1.3.1, 1.3.1 -server, 1.4.0, 1.4.0 -server and 1.4.0 -Xint. This is probably the most useful part of the book, and also lends credibility to the statements that Jack makes. If Jack makes a statement without proof, you cannot assume that it is true. However, when the statement is backed up with values, you can be assured that it is probably correct.
Kent Beck is famous for his phrase: "Make it run, make it right, make it fast."
Unless we can compile it, we do not need to try tune the performance. Once we get it to compile, we have to make sure that it is correct. What is the point of having a blazingly fast algorithm that is incorrect? I can sort any collection of elements in O(1) if it does not have to be correct.
In my doctoral thesis, I examined how one could analytically evaluate the performance of a distributed system written in the Specification and Discription Language (SDL). Since there were few analytical techniques for performance evaluation of SDL systems, I converted SDL to a new form of Petri net based on the Queuing Petri Net invented by the University of Dortmund. There are many correctness and performance testing techniques for Petri nets, so the first step was to determine whether the net contained deadlocks, was unbounded or had livelocks. Once the correctness had been determined, I could apply the numerous performance techniques to the net and then convert the results back to the original SDL system.
Due to the complexity of the Markov Chain that was generated from the Petri net, we could only analyse small systems, but then, it was a doctoral thesis, not a commercial product. You are allowed to dream a bit in theory when you do a PhD, infact, a PhD is where you investigate everything about nothing.
In the real world, we usually stop after the first test: "does it compile?" Most software in the world has not been tested before being delivered. Why? Because testing that it works correctly is a tedious job that takes time and programmers do not make mistakes anyway. I am yet to walk into a company and ask whether they have unit tests, without getting a nervous reaction. When I then ask what they are doing about performance I get an even more nervous reaction.
When the system then performs badly, they have to call in trouble-shooters like Jack and myself to tune their applications. Performance involves both CPU times and memory sizes. A few newsletter ago, I casually asked whether you wanted to know how to detect OutOfMemoryErrors ahead of time. More than 100 readers responded that they were in desperate need of such a tool. Jack and I do not mind this state of affairs at all - that is a good way to earn our keep. However, you will have a problem when you need Jack and me but you cannot afford our rates. Then you will kick yourself for putting performance last.
I asked Jack about the philosophy of "Make it run, make it right, make it fast" and Jack mentioned to me that there are situations where this does not work. There are ways in which you can paint yourself in a corner so that the only alternative is to throw away the application and start again. This piqued my curiosity, and I asked Jack for an example.
"An example of painting yourself in the corner is when you do not use the Page-by-Page Iterator Design Pattern from the beginning"
As I thought about the troubled projects I had consulted for, I realised that Jack was correct. You can get a better optimising compiler, you can reduce the number of Strings that get created, you can change the collection you use, but if you do not have the Page-by-Page Iterator Design Pattern in your design from the beginning, you will be p*ssing against the wind. This applies for 3-tier and for 2-tier applications. The P-b-P Iterator can be added to your system after-the-fact, but it will require plenty of work and cause errors.
Should you purchase the Java Performance Tuning Book [ISBN 0596003773] ? There is no easy answer to that question. If you do not have the US$ 44.95 to pay for the book, it will be unlikely that you will be able to afford to pay for Jack or me to help you tune your system. Basically, you would be on your own. You can also browse Jack's website or look at our newsletter archive. Otherwise, if you are serious about writing good Java code that is well-tuned, you would do well to purchase a copy of Jack's book, and read through it over a period of a few months.
That is all I have to say on this topic. The book could be converted into about 30 newsletters, that is how much information is contained therein.
Kind regards
Heinz
We are always happy to receive comments from our readers. Feel free to send me a comment via email or discuss the newsletter in our JavaSpecialists Slack Channel (Get an invite here)
We deliver relevant courses, by top Java developers to produce more resourceful and efficient programmers within their organisations.