Slashdot Mirror


Java Performance Tuning, 2nd Ed.

cpfeifer writes "Performance has been the albatross around Java's neck for a long time. It's a popular subject when developers get together "Don't use Vector, use ArrayList, it's more efficient." "Don't concatenate Strings, use a StringBuffer, it's more efficient." It's a chance for the experienced developers to sit around the design campfire and tell ghost stories of previous projects where they implemented their own basic data structures {String, Linked List...} that was anywhere from 10-50% faster than the JDK implementation (and in the grand oral tradition of tall tales, it gets a little more efficient every time they tell it)." Want to kill the albatross? Read on for the rest of cpfeifer's review of O'Reilly's Java Performance Tuning, now in its 2nd edition. Java Performance Tuning, 2nd Edition author Jack Shirazi pages 570 publisher O'Reilly and Associates rating 9/10 reviewer cpfeifer ISBN 096003773 summary It's the most up to date publication dealing specifically with performance of Java applications, and is a one of a kind resource.

Every developer has written a microbenchmark (a bit of code that does something 100-1000 times in a tight loop and measure the time it takes for the supposed "expensive operation") to try and prove an argument about which way is "more efficient" based on the execution time. The problem, is when running in a dynamic, managed environment like the 1.4.x JVM, there are more factors that you don't control than ones that you do, and it can be difficult to say whether one piece of code will be "more efficient" than another without testing with actual usage patterns. The second edition of Review of Java Performance Tuning provides substantial benchmarks (not just simple microbenchmarks) with thorough coverage of the JDK including loops, exceptions, strings, threading, and even underlying JVM improvements in the 1.4 VM. This book is one of a kind in its scope and completeness.

The Gory Details
The best part of this book is that it not only tells you how fast various standard Java operations are (sorting strings, dealing with exceptions, etc.), but he has kept all of the timing information from the previous edition of the book. This shows you how the VMs performance has changed from version 1.1.8 up to 1.4.0, and it's very clear that things are getting better. The author also breaks out the timing information for 3 different flavors of the 1.4.0 JVM: mixed interpreted/compiled mode (standard), server (with Hotspot), and interpreted mode only (no run time optimization applied).

Part 1 : Lies, Damn Lies and Statistics
The book starts off with three chapters of sage advice about the tools and process of profiling/tuning. Before you spend any time profiling, you have to have a process and a goal. Without setting goals, the tuning process will never end and it will likely never be successful.

The author outlines a general strategy that will give you a great starting point for your tuning task forces. Chapter 2 presents the profiling facilities that are available in the Java VM and how to interpret the results, while chapter 3 covers VM optimizations (different garbage collectors, memory allocation options) and compiler optimizations.

Part 2 : The Basics
Chapters 4-9 cover the nuts and bolts, code-level optimizations that you can implement. Chapter 4 discusses various object allocation tweaks including: lazy initialization, canonicalizing objects, and how to use the different types of references (Phantom, Soft, and Weak) to implement priority object pooling. Chapter 5 tells you more about handling Strings in Java that you ever wanted to know. Converting numbers (floats, decimals, etc) to Strings efficiently, string matching -- it's all here in gory detail with timings and sample code.

This chapter also shows the author's depth and maturity; when presenting his algorithm to convert integers to Strings, he notes that while his implementation previously beat the pants off of Sun's implementation, in 1.3.1/1.4.0 Sun implemented a change that now beats his code. He analyzes the new implementation, discusses why it's faster without losing face. That is just one of many gems in this updated edition of the book. Chapter 6 covers the cost of throwing and catching exceptions, passing parameters to methods and accessing variables of different scopes (instance vs. local) and different types (scalar vs. array). Chapter 7 covers loop optimization with a java bent. The author offers proof that an exception terminated loop, while bad programming style, can offer better performance than more accepted practices.

Chapter 8 covers IO, focusing in on using the proper flavor of java.io class (stream vs. reader, buffered vs. unbuffered) to achieve the best performance for a given situation. The author also covers performance issues with object serialization (used under the hood in most Java distributed computing mechanisms) in detail and wraps up the chapter with a 12 page discussion of how best to use the "new IO" package (java.nio) that was introduced with Java 1.4. Sadly, the author doesn't offer a detailed timing comparison of the 1.4 NIO API to the existing IO API. Chapter 9 covers Java's native sorting implementations and how to extend their framework for your specific application.

PART 3 : Threads, Distributed Computing and Other Topics
Chapters 10-14 covers a grab bag of topics, including threading, proper Collections use, distributed computing paradigms, and an optimization primer that covers full life cycle approaches to optimization. Chapter 10 does a great job of presenting threading, common threading pitfalls (deadlocks, race conditions), and how to solve them for optimal performance (e.g. proper scope of locks, etc).

Chapter 11 provides a wonderful discussion about one of the most powerful parts of the JDK, the Collections API. It includes detailed timings of using ArrayList vs. LinkedList when traversing and building collections. To close the chapter, the author discusses different object caching implementations and their individual performance results.

Chapter 12 gives some general optimization principles (with code samples) for speeding up distributed computing including techniques to minimize the amount of data transferred along with some more practical advice for designing web services and using JDBC.

Chapter 13 deals specifically with designing/architecting applications for performance. It discusses how performance should be addressed in each phase of the development cycle (analysis, design, development, deployment), and offers tips a checklist for your performance initiatives. The puzzling thing about this chapter is why it is presented at the end of the book instead of towards the front, with all of the other process-related material. It makes much more sense to put this material together up front.

Chapter 14 covers various hardware and network aspects that can impact application performance including: network topology, DNS lookups, and machine specs (CPU speed, RAM, disk).

PART 4 : J2EE Performance
Chapters 15-18 deal with performance specifically with the J2EE APIs: EJBs, JDBC, Servlets and JSPs. These chapters are essentially tips or suggested patterns (use coarse-grained EJBs, apply the Value Object pattern, etc) instead of very low-level performance tips and metrics provided in earlier chapters. You could say that the author is getting lazy, but the truth is that due to huge number of combinations of appserver/database vendor combinations, it would be very difficult to establish a meaningful performance baseline without a large testbed.

Chapter 15 is a reiteration of Chapter 1, Tuning Strategy, re-tooled with a J2EE focus. The author reiterates that a good testing strategy determines what to measure, how to measure it, and what the expectations are. From here, the author presents possible solutions including load balancing. This chapter also contains about 1.5 pages about tuning JMS, which seems to have been added to be J2EE 1.3 acronym compliant.

Chapter 16 provides excellent information about JDBC performance strategies. The author presents a proxy implementation to capture accurate profiling data and minimize changes to your code once the profiling effort is over. The author also covers data caching, batch processing and how the different transaction levels can affect JDBC performance.

Chapter 17 covers JSPs and servlets, with very little earth shattering information. The author presents tips such as consider GZipping the content before returning it to the client, and minimize custom tags. This chapter is easily the weakest section of the book: Admittedly, it's difficult to optimize JSPs since much of the actual running code is produced by the interpreter/compiler, but this chapter either needs to be beefed up or dropped from future editions.

Finally, chapter 18 provides a design/architecture-time approach towards EJB performance. The author presents standard EJB patterns that lend themselves towards squeezing greater performance out of the often maligned EJB. The patterns include: data access object, page iterator, service locator, message facade, and others. Again, there's nothing earth shattering in this chapter. Chapter 19 is list of resources with links to articles, books and profiling/optimizing projects and products.

What's Bad?

Since the book has been published, the 1.4.1 VM has been released with the much anticipated concurrent garbage collector. The author mentions that he received an early version of 1.4.1 from Sun to test with. However, the text doesn't state that he used the concurrent garbage collector, so the performance of this new feature isn't indicated by this text.

The J2EE performance chapters aren't as strong as the J2SE chapters. After seeing the statistics and extensive code samples of the J2SE sections, I expected a similar treatment for J2EE. Many of the J2SE performance practices still apply for J2EE (serialization most notably, since that his how EJB, JMS, and RMI ship method parameters/results across the wire), but it would be useful to fortify these chapters with actual performance metrics.

So What's In It For Me?

This book is indispensable for the architect drafting the performance requirements/testing process, and contains sage advice for the programmer as well. It's the most up to date publication dealing specifically with performance of Java applications, and is a one-of-a-kind resource.

You can purchase Java Performance Tuning, 2nd Edition from bn.com. Slashdot welcomes readers' book reviews -- to see your own review here, read the book review guidelines, then visit the submission page.

18 of 287 comments (clear)

  1. Isn't this the compiler's job? by chrisseaton · · Score: 4, Insightful

    If all these performance hacks are documented, why doesn't the compiler implement them?

    I've often found that will bytecode languages (Java, C#...) the bytecode instructions are made for the language so that the compiler can just throw them out easy peasy, but they seem to overlook the sort of optimizations that C compilers, for example, work hard to implement.

    1. Re:Isn't this the compiler's job? by cmburns69 · · Score: 4, Informative

      With non-bytecode langauges, the compiler can optimize to the environment. It can re-order code based on the fastest execution time for the platform the code is compiled for.

      Java (and other bytecode languages) were desinged to run well not just on a single platform, but on a variety of platforms. So as a trade-off, you lose environment-specific optimizations at compile time.

      JIT JRE/compilers can work to prevent this. They can further optimize the bytecodes at execution time because they are platform specific.

      An online Starcraft RPG? Only at
      In soviet Russia, all your us are belong to base!

      --
      Online Starcraft RPG? At
      Dietary fiber is like asynchronous IO-- Non-blocking!
    2. Re:Isn't this the compiler's job? by briaydemir · · Score: 5, Insightful

      If all these performance hacks are documented, why doesn't the compiler implement them?

      The most common reason is that most performance hacks and optimizations are not decidable, and you want a compiler to implement only decidable algorithms becuase those are the ones that enable a compiler to be deterministic. It is usually much easier for a person, i.e., human, to determine what can be done, than it is for a machine to determine that exact same thing.

      Consider the following piece of code.

      boolean f(int[] a, int[] b)
      {
      int x = a[0];
      b[0] = a[0] + 2;
      int y = a[0];
      return (x == y);
      }

      Does f always return true? Only if we can prove that a and b never point to the same array. A person maybe able to do this, but a machine would have great difficulty (assuming the machine could even do it).

      So to sumarize, compiler's don't implement many optimization hacks becuase then they might not be deterministic, and that is a bad thing.

  2. Correct ISBN is 0596003773 by zipwow · · Score: 5, Informative

    The bn.com link is broken for me, here's the correct ISBN:

    0596003773

    --
    I don't know which is more depressing, that 2/3 didn't care enough to vote, or that 1/2 of those that did are crazy.
  3. What performance issues? by Anonymous Coward · · Score: 5, Funny

    Java has performance troubles? I thought we were all supposed to deny that. Did I miss a memo or something?

  4. Insightful? by rfischer · · Score: 4, Insightful

    Remember there is a distinction between client- and server-side Java. Java on the server makes me very happy.

  5. String/StringBuffer by toriver · · Score: 4, Informative

    It does under the hood whenever you use + for concatenation; this is why using String + String in a loop is ineffective: You create a new StringBuffer object per iteration. The solution in this case is to declare the StringBuffer outside the loop and use append() explicitly within.

    For concatenating two strings, the concat() method can be faster than using StringBuffer, since it only needs to create a new char[] and do a (fast) arraycopy from the two internal arrays.

    Also, everyone should be aware of the 1.4.1 memory leak associated with using StringBuffer's toString() and setLength() methods.

  6. Who cares? by Elgon · · Score: 5, Insightful

    Okay,

    flippant comment but let's think about this for a second: The majority of the time the alleged efficiency advantage is small or, as is generally the case, a pointless optimisation. Java coders seem to have the major efficiency/speed hangup - they use it to lord it over scripting programmers but they want/lack/desire the swiftness of C. (And yes, I do program in Java.)

    To my mind, this is approching the problem from entirely the wrong direction: CPU time and CPU power are far cheaper than developer time and designer time. Therefore, rather than use some cobbled-together hack, use the standard implementations and take the performance hit.

    This will be cheaper, probably 95% as efficient and, most importantly, be 195% easier to maintain or change at a later date. Consider the big picture rather than a single aspect.

    NB - YMMV, for certain apps, it really does make sense to break all of the above ideas and principles, but if you REALLY need it to run that fast, you should be using C anyway.

    Elgon

    1. Re:Who cares? by Jord · · Score: 4, Insightful
      True, going back and fine tuning to gain a 2% speed increase (example) is a waste of time. However the value I see in books like this is in training/teaching the developer to write more efficient code the first go around. If you get out of the habit of doing String + String + String and use StringBuffers instead your code is more efficient from the beginning.

      That is the value I see from books like this.

  7. Re:idiots.; by iapetus · · Score: 4, Insightful

    Because the sort of people who like to get involved in discussions about whether C# is 'better' than Java or Java is 'better' than Perl or crunchy peanut butter is 'better' than textured masonry paint can't cope with more than one thing at a time, and tend to apply their religious zealotry with great vigour.

    Those of us who can program in more than one language and know that sometimes it's a matter of choosing the right tool for the job (peanut butter for sandwiches, masonry paint for walls) tend to go through three stages:

    1) Try to engage in such discussions on the premise that there's actual intelligent debate going on.

    2) Discover ourselves becoming violently opposed to whatever rant we're reading at the time, writing tracts about how Java sucks when we're reading the work of a Java fanatic and drooling about the glory of Java when faced with a C++-toting moron.

    3) Either give up in disgust and let the language fanboys get on with it, or sit on the sidelines and snipe at both sides - similar to stage 2, but more consciously applied. Normally that progresses towards giving up, though, since the zealots are just too easy and predictable...

    --
    ++ Say to Elrond "Hello.".
    Elrond says "No.". Elrond gives you some lunch.
  8. Re:Java doesn't cut it by AirLace · · Score: 4, Insightful

    You could have saved yourself some porting by just compiling your java code with GCJ. GCJ allows you to compile your java byte code to native executables.

    This might become an option in a few years, but the GNU classpath is as yet not complete enough for our years. We actually didn't find gcj output that performant, despite it being compiled to native code. The JRE still beat it in many cases.

    Use SWT with Java. SWT uses Windows native widgets on Windows or GTK on Linux.

    We also investigated this. SWT is a _horrendous_ API which offers very little abstraction. You end up writing your code once for the Gtk+ target, and again for the native Windows target. It isn't really a cross-platform abstraction like WxWindows, and it's probably the reason why the Eclipse codebase is so large. You end up writing your application for each UI target platform. Gtk# runs and integrates with the platform instead, so you only write your code once.

    Either your telling a big lie or dont have your facts straight. Unless you can show hard facts your not going to sway anyone into believing interpreted code outperformed compiled.

    I did mention the results are empirical, but they're also pretty obvious from where I stand. You don't need benchmarks when something performs, in some cases, eight times faster than the original implementation. I may well put togther some benchmarks and post them to mono-list or linuxtoday.com. I don't have benchmarks yet; does that make me a liar? Sigh.

    What is exactly wrong with Java's use of native threads on Linux boxes?

    It's pointless to interface with the threads layer directly when pthreads exists. It makes the runtime essentially unportable to other unices/operating systems. Mono plays nicely with the environment, so the runtime can just be compiled on any POSIX-compilant system. Linux is great, but being attached to it so firmly that your application breaks when Linus changes some internal interfaces is not.

  9. Re:More efficient != better by blamanj · · Score: 5, Informative
    Actually,that's exactly what the compiler does. The problem occurs in cases like this:
    String foo = "";
    while (source.hasMoreTokens()) {
    foo += source.nextToken();
    }
    where you are creating a destroying a large number of strings. In this case, using a StringBuffer is far more efficient and doesn't really harm readability.

  10. It's all about the VM by slagdogg · · Score: 4, Insightful

    I read the first edition of this book completely. There are some good tips for extracting a few percentage points of improved performance. However, nothing has as profound an impact as simply using a better VM ... for example, many of my applications saw 25%+ speed increases simply by switching from the 1.2.x series VM to the 1.3.x series VM. Java does a pretty could job as a language of encouraging best practices, i.e. the inclusion of a standard StringBuffer. Extreme optimization at the code level will always be limited given the high abstraction of the language. However, extreme optimization at the VM level is a very real thing, and it doesn't take a whole lot of effort for the Java programmer.

    --
    (Score:-1, Wrong)
  11. Re:Oxymoron ? by wfmcwalter · · Score: 4, Interesting
    I don't quite know what it is that they've improved, but JVM startup time seems to have gotten dramatically better somewhere betweek JDK1.3 and JDK1.4.1. On my mainstream winXP machine I can have a text-mode java HelloWorld running to completion in 0.2 secs (tested using cygwin's "time" command). That's a huge improvement over the several seconds it used to take, and makes writing little command line utilities in java a practical prospect.

    Previously, the startup slowdown was due to the system having to load, verify, and link the twenty or so classes a simple program depends upon. Pjava and J2ME-CDC solved that by storing an image of the heap with the system classes already loaded, verified, and linked (and quickened) so the system was run-ready almost immediately. I wonder if the J2SE folks picked up on that? Alternatively, they could just be skipping the verify for those classes in the signed rt.jar, and offline preverify them prior to signature - the verifier always was the slow part of the process.

    Your point about threads is well taken, and applies more generally to much of java programming. Java's language and libraries make it all to easy to write architecturally-slow programs - you really still have to fully understand what you're doing in order to write a decent program, regardless of the language.

    --
    ## W.Finlay McWalter ## http://www.mcwalter.org ##
  12. Re:Sysadmins don't buy into this article. by markv242 · · Score: 5, Interesting
    I forwarded this article to our sysadmins, and they basically laughed it off. At least for web-based applications, almost none of these points apply. The article talks about the author's hatred of JVM startup times (which don't apply for an application server) and exception verbosity (which any real java developer will catch and deal with himself, rather than leaving it up to the sysadmin(?) to deal with).

    I challenge you to make a C++/C# application that is thread-safe and can scale to millions of pageviews per day without writing a ton of supporting code. With a good J2EE app server, a java coder essentially just has to wrap his thread-unsafe code in a syncronized() statement, and he's done-- his app is now thread-safe.

    Additionally, the "cross-platform doesn't matter for sysadmins" is a false statement; our CIO asked our net ops group "what would be the impact of us moving to an Intel platform?" and our sysadmins (after consulting with the coders) replied "absolutely no impact". That made our CIO very, very happy. Again, I challenge you to move your C++ apps from Solaris to Linux, or even to Windows, without any hiccup.

    All of these other arguments are very specious: "I don't have enough RAM" will get you a reply of "go down to Fry's and spend $125 on another GB" every time. Processor speeds, even on Sun boxes, is getting to the point where the processor will never be a bottleneck for anything. Sure, java won't run as fast as a natively-compiled app. Neither will perl, php, tcl, or what have you. Raw processor speed is not as important when you have a couple of GHz to play with.

  13. Re:Don't use Java.... by boomgopher · · Score: 4, Insightful

    Java would've been far better if they'd stuck to a few basic classes, and let people develop the classes they need as they go.

    Well, gosh, you go right ahead and write your own replacement classes for everything that Sun has done already. What's stopping you?

    That's exactly why I like Java. They have a lot of good built-in libraries that cover a wide-range of applications. I don't have to reinvent the freaking wheel every time I write an app.

    --
    Your hybrid is not saving the environment. Its purpose is to make you feel good about buying something.
  14. Re:Pre-written appendix for Java Tuning by egomaniac · · Score: 4, Informative

    That article is the most absurd joke I have ever read. He spends half the article complaining about Java's startup time (which (A) does not apply in any server situation, and (B) is unfair, because you don't count the machine's bootup time when talking about the performance of C programs, do you?).

    Then he invents other ways to talk about the startup time without seeming to talk about the startup time (for instance, trussing Hello World results in a ton of output, but naturally that's Java starting up and loading its classes. Again, do you consider what the machine has to do to boot itself up when you're talking about C programs?). I will point out again that Java's startup time is almost irrelevant, especially in a server environment (which is what he's talking about).

    The rest of the article is picking on the "jar" tool. jar is a program written in Java. Criticisms against the jar tool no more reflect on Java than criticisms against gzip reflect on C. The fact that jar doesn't do a good job of reporting errors is (A) irrelevant, because it's a developer tool and we know how to read exceptions, and (B) still more irrelevant, because how well it reports errors has nothing to do with what language it was written in. Tons of C programs have lousy error reporting as well, such as a number of Unix utilities I might name.

    Further, this article is obviously very old. He's talking about Java 1.1.8, which is what, five years old now? Might as well criticize Linux by talking about obscure video driver bugs that were fixed five years ago. Obviously, that's not the article's fault for having been written so long ago, but it is the parent poster's fault for bringing it up as if it is somehow still relevant.

    --
    ZFS: because love is never having to say fsck
  15. Re:Java is plenty fast by be-fan · · Score: 4, Informative

    The FFT benchmark is a very specific case. Once the JIT kicks in, it's not Java vs C++ anymore, it's the JVM optimizer vs the GCC one. Contrary to popular belief, the GCC optimizer is very good (check out benchmarks vs ICC at coytegulch.com). However, the FFT benchmark is a case where the additional information available to the JIT optimizer allows it to outperform native code. The whole benchmark is so small, it probably even fits in cache, and doesn't really stress any of the performance pitfalls of the language itself. Now, if you have a larger application, that doesn't consist of a single inner-loop, and meanders through a lot of varied code (ie. most real applications) then the performance story will be very different. At that point, Java's performance faults (excessive bookkeeping overhead, object allocation/deallocation, overhead from the JVM, etc) come much more into play.

    --
    A deep unwavering belief is a sure sign you're missing something...