[H-GEN] Which is better?

Adrian Sutton adrian at intencha.com
Tue Apr 27 21:10:48 EDT 2004


> It turns of the same thing effects C#.  It is particularly noticable in
> in things like bringing up a form, an operation that does 1000's of
> memory allocations.  The interesting thing about C# is that you can't
> blame the GUI this time - its all done by Windows.  If you assume the
> JIT is as good as Java's (that possibly is a big assumption), then that
> only leaves one thing, doesn't it?  Memory allocation.

It leaves a heck of a lot more than memory allocation.  Firstly, if the 
GUI is entirely done by Windows then the memory allocation isn't a 
factor because it's all handled by the same code that is used if you 
write an application in C.  Secondly, the overhead of converting types 
from one language to another incurs a major performance hit.  In this 
case, you have to bridge between .Net's runtime and the native Windows 
libraries.  With Java you have to use JNI.  Both incur a performance 
penalty which would be large enough to explain the slower speed.  The 
only way to actually find out what is taking the time requires you to 
profile the application in question and find out.

>
>> If you haven't profiled it, you don't know what you're talking about.
>
> Excellent advice.  So lets profile it, shall we?  God knows, there have
> been enough people preaching about "premature" optimisation on this
> thread, so lets actually do some testing rather than just waffling on.
> At the very end of this post is a java program.  It runs some tests and
> times them.  Under java 1.4.1 this is its output:
>
>   dim unoptimised:     32212
>   dim optimised:        1482
>   format unoptimised: 343447
>   format optimised:    90374
>   Array unoptimised:    1570
>   Array harry:          1439
>   Array aj:             1456
>   Array david1:         1432
>   Array david2:         1457
>   Array david3:         1523

None of these times mean anything.  Firstly, you're benchmarking not 
profiling.  Profiling is the process of measuring objectively the time 
taken by a program in particular sections in order to identify 
performance problems.  Benchmarking on the other hand is the process of 
writing simple, non-real world tests and timing them.  Benchmarks are 
not useful, particularly with Java.

The reason benchmarks are so useless with Java and the reason your 
results are completely meaningless is because the behavior of the 
garbage collector is completely uncontrolled.  In your tests you are 
creating a whole bunch of objects but not controlling when or if the 
garbage collector will run.  Thus, at any point in any of the tests the 
garbage collector may kick in either for a partial sweep or a full 
sweep which will most likely dwarf the time taken actually running the 
test.  Further to this, the JIT may kick in at any time and completely 
change the actual code that's being run.  Also consider the fact that 
the first test to run must load the relevant classes, however this does 
not affect the other tests because they are already loaded.  Finally 
add in the fact that System.currentTimeMillis() is notoriously 
inaccurate.


The actual times you've received show no significant difference between 
any of array tests.  The format tests is really a test of doing a whole 
lot of string parsing compared to doing no string parsing (surprise, 
doing no string parsing is faster).  That leaves the Dimension tests.  
Now, if you were an optimizing compiler and came across the code:

>       for (int i = 0; i < 10000; i += 1)
> 	continue;
>     }

Would you run it?  No, this code will most likely be completely removed 
once the JIT kicks in on that piece of code.  Thus, you're comparing 
creating 10000 Dimension objects to doing nothing.  The result is no 
surprise.  Further to this, in the first test you are benchmarking both 
the time required to perform memory allocation (your intended 
benchmark) plus the time required to garbage collect.  Depending on the 
size of the young generation you may well have overflowed it and wound 
up using the older generation thus requiring a full GC run rather than 
the faster young generation run.

Worst of all, the dim unoptimized test must first initialize the entire 
AWT libraries before it can even begin, which would explain the entire 
difference in that test.

>   - Harries optimisation speed up by a factor of 14%.  This was
>     surprising to me.  It means the JIT was not doing any
>     global flow analysis.

In your specific example, on your specific platform, with your specific 
amount of RAM and with your specific CPU speed and architecture.  The 
fact that Java 1.4.2 adds further optimization strategies to the JIT 
should not go unnoticed either.  As mentioned above however, it most 
likely means that the garbage collector didn't kick in during Harry's 
test but left those objects to be cleaned up during a different test.

>   - Aj's optimisation was not quite as good as Harries, but still
>     an improvement.  The old C coding tricks still work in java
>     evidently.  Harry - if you still feel the urge to optimise
>     your for loops use this.  It doesn't harm readability and is
>     faster.

This most definitely does harm readability.  Most programmers will 
expect that the iteration of an array will occur incrementally, not 
decrementally.  By reversing the order of the iteration you break that 
expectation and make the programmer stop and think about exactly what 
the code does as they no longer recognise the pattern for a for loop 
iterating over an array.  Again, there is You're talking about a speed 
up of 0.1 of a second even if you consider the measurements above to be 
valid.

>   - The real surprise came from testing David's conjecture.  I would
>     of predicted that given a modern compiler there should be no
>     difference.  The intent of the code should be obvious to any
>     compiler.  Yet the results differ markedly.  This puts a dent
>     in the argument that optimisation should be left to the compiler.
>     I am not sure Java is doing any.  A far better argument is that
>     machines run so quickly nowadays that optimising your code is not
>     worth the effort.

There is no difference in the times.  Further to this, the point at 
which Java performs it's optimizations is undefined.  There is no way 
to tell if it would have optimized it further or not.  Assuming that 
compilers and JITs don't optimize code is just plain idiocy as it's 
clear that there are so many people employed (with Sun in this case) to 
implement these optimization routines.

> Anyway - the lesson I take from all this is that it is still worth
> applying coding patterns like AJ's.  Its gives you a minor speed up to
> your program at no expense in readability.

I would definitely disagree.  There is no indication that there is any 
performance benefit and by not using the most common form for a 
construct, you do incur an readability penalty.

> All bullshit aside memory
> allocation in Java is a relatively expensive operation, and like all
> expensive operations should be reduced where possible.

I continue to disagree strongly on this point.  None of these figures 
provide evidence to support that theory.  The only way to identify 
performance bottlenecks in your program is to take the real program and 
profile it.  As I mentioned previously, there has only been a single 
case in my experience where the bottle neck turned out to be memory 
related and in that case it was garbage collection rather than memory 
allocation.

>   Not at the
> expense of good design, of course, but then a good algorithm tend to
> avoid expensive operations so the two goals don't totally conflict.
> Unfortunately, the term "at the expense of" is subjective, and a feel
> for it only comes with experience.

If it doesn't need to be optimized, don't.  Write the clearest code you 
can, follow a good design and select your algorithms carefully.  It is 
possible to analyze the performance of algorithms in mathematical terms 
(O(n), O(n^2) etc).  Trying to micro-optimize code before you know 
where the real bottle necks are is a waste of your time and detracts 
from the readability of the code.

> By the by, I wrote the "dim unoptimised" test in C.  It ran 5,000 times
> faster than the Java version.  Source code appears at the end.

This doesn't surprise me at all.  Here's what the C code does:

* Check the RAM is available, if not crash.
* Mark this section of RAM as in use.
* Mark this section of RAM as not in use.

Here's what the Java version does:

* Check the RAM is available, if not:
     * Run a partial sweep of the garbage collector.  If there is still 
not enough ram, run a full GC.
     * Call finalize() on all objects which are marked for deletion, 
incurring the cost of a dynamic method lookup.
     * Check to see if this object is referenced with any weak or soft 
references.  If so, add the object to the queue for that reference.
     * Mark the section of RAM used by each object as not in use.
* Mark this section of RAM as in use.
* Create the Object structure within that RAM.
* Create the Dimension structure in RAM, including two ints and a long 
variable.
* Execute the static block in Dimension (including initializing the 
GUI):
    static {
         /* ensure that the necessary native libraries are loaded */
     Toolkit.loadLibraries();
         if (!GraphicsEnvironment.isHeadless()) {
             initIDs();
         }
     }
* Call the constructor Dimension().
* Call the constructor Dimension(int, int) - called by Dimension().
* Perform two integer assignments.
* Pass a reference to the new Object to the garbage collector or to a 
parent object so it can be collected later.
* When the loop is finished, mark the entire amount of RAM allocated to 
the JVM as not in use and exit.

At any point in that process the GC may kick in and perform the 
indented actions.  It becomes exceptionally clear that memory 
allocation is the least of your worries.

And just as I'm about to send this:

On Wed, 2004-04-28 at 10:14, Russell Stuart wrote:
>> By the by, I wrote the "dim unoptimised" test in C.  It ran 5,000 
>> times
>> faster than the Java version.  Source code appears at the end.
>
> Dam, dam, dam.  It didn't run 5,000 times faster.  It ran about 30%
> slower(!).  I forgot about the nested for loops in the Java version.

Says it all really. :)

Regards,

Adrian Sutton.
----------------------------------------------
Intencha "tomorrow's technology today"
Ph: 38478913 0422236329
Suite 8/29 Oatland Crescent
Holland Park West 4121
Australia QLD
www.intencha.com





More information about the General mailing list