how slow is exceptions really compared?

T

tom fredriksen

I was reading about exceptions and in the book "Effective Java" it is
stated that exceptions are terribly slow, about 79 times slower than the
FAST example below. Now i tested it and I cant see any difference. I
have heard this many times, but I am not sure how much I believe it. So
what is it that i so slow about exception. I understand that creating an
exception object is slow and sending it up the stack for processing at
every point is also slow. But as far as I can see that would only matter
if you used eceptions for flow control (i.e. for every iteration (for
some strange reason) or something similar). I dont see how it can be any
slower to use it as described in the SLOW code below (I know it is not
the correct way to do it, idiom wise). Any comments?

/tom

SLOW

try {
int i = 0;
while(true)
a[i++].f();
} catch(ArrayIndexOutOfBoundsException e) {
}


FAST:

for (int i = 0; i < a.length; i++)
a.f();
 
T

Timo Stamm

tom said:
I was reading about exceptions and in the book "Effective Java" it is
stated that exceptions are terribly slow

This has been true in early versions of java.

But as far as I can see that would only matter
if you used eceptions for flow control (i.e. for every iteration (for
some strange reason) or something similar).

Exceptions are ment to handle exceptional control flow. Using them for
conventional flow control is bad design. So even if Exceptions were
still as slow as they have been five years ago, it should not not really
matter.


"Effective Java" doesn't seem to be a recommendable book.


Timo
 
M

Mike Schilling

tom fredriksen said:
I was reading about exceptions and in the book "Effective Java" it is
stated that exceptions are terribly slow, about 79 times slower than the
FAST example below. Now i tested it and I cant see any difference. I have
heard this many times, but I am not sure how much I believe it. So what is
it that i so slow about exception. I understand that creating an exception
object is slow and sending it up the stack for processing at every point
is also slow. But as far as I can see that would only matter if you used
eceptions for flow control (i.e. for every iteration (for some strange
reason) or something similar). I dont see how it can be any slower to use
it as described in the SLOW code below (I know it is not the correct way
to do it, idiom wise). Any comments?

/tom

SLOW

try {
int i = 0;
while(true)
a[i++].f();
} catch(ArrayIndexOutOfBoundsException e) {
}


FAST:

for (int i = 0; i < a.length; i++)
a.f();


If you're timing the running of programs that do nothing but that code, you
won't see much difference, since the startup cost dwarfs everything else.

Try this test:

class Timing
{
public static void main(String[] args) throws Exception
{
int[] a = new int[10];

long start = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++)
{
try
{
int j = 0;
while(true)
{
a[j] = j;
j++;
}
}
catch(ArrayIndexOutOfBoundsException e)
{
}
}
System.out.println(
"Slow time is " + (System.currentTimeMillis() - start) +
" milliseconds.");

start = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++)
{
for (int j = 0; j < 10; j++)
{
a[j] = j;
}
}
System.out.println(
"Fast time is " + (System.currentTimeMillis() - start) +
" milliseconds.");
}
}

I get output like

Slow time is 6843 milliseconds.
Fast time is 63 milliseconds.
 
M

Mike Schilling

"Effective Java" doesn't seem to be a recommendable book.

Are you kidding? It's one of the best Java books ever written. It's true
that it could stand updating, as some of the details are no longer accurate,
but overall it's check-full of good advice. In fact, the instructor at a
Microsoft course I once took called in the best C# book he knew of, because
so much of it is also applicable to .NET.
 
T

Timo Stamm

Mike said:
I get output like

Slow time is 6843 milliseconds.
Fast time is 63 milliseconds.


I get output like

Slow time is 65 milliseconds.
Fast time is 49 milliseconds.

(Run on a 4 processor 64 bit machine, so server vm is enabled by default
and the JIT kicks in.)


Micro-optimizations like using your own constructs instead of Exceptions
are a total waste of time. Even worse, they make the code difficult to
maintain.


Timo
 
M

Mike Schilling

Timo Stamm said:
I get output like

Slow time is 65 milliseconds.
Fast time is 49 milliseconds.

(Run on a 4 processor 64 bit machine, so server vm is enabled by default
and the JIT kicks in.)


Micro-optimizations like using your own constructs instead of Exceptions
are a total waste of time. Even worse, they make the code difficult to
maintain.

Using a for loop to navigate through an array is "my own construct" and a
"micro-optimization" that's "hard to maintain"? Good Lord.
 
T

Timo Stamm

Mike said:
Are you kidding? It's one of the best Java books ever written.

I haven't read it, but what Tom reported sounded a lot like propagation
of micro-optimizations.


Timo
 
M

Mike Schilling

Timo Stamm said:
I haven't read it, but what Tom reported sounded a lot like propagation of
micro-optimizations.

Do you often judge books by second-hand reports of small sections of them?

Just asking.
 
R

Roedy Green

Now i tested it and I cant see any difference.
The problem is compilers/JITs/AOTs are getting smarter. They easily
ultra- optimise contrived examples they could not tidy up in real
world situations.
 
T

Timo Stamm

Mike said:
Using a for loop to navigate through an array is "my own construct" and a
"micro-optimization" that's "hard to maintain"?

Obviously not. Are you trolling? Here is an explanation for the interested:

A "micro-optimization" is a performance optimization at a low level. For
example, the following construct:

for( int i = upperbound; 0 != i--; ) {
// ...
}

is supposed to be more efficient than

for( int i = 0; i < upperbound; i++ ) {
// ...
}


This is actually true, the former construct can be more efficient in
statically compiled languages. But in a language like Java, which is
compiled into byte code and interpreted by a virtual machine that can
theoretically run on any platform, such optimizations don't make much
sense for several reasons:

The latter construct is standard - every programmer who knows languages
influenced by C will understand it at the first glance. Using your own
constructs will make the code harder to read for anybody else, which may
lead to more time-consuming maintenance and more bugs.

Additionally, you can not even be sure that this micro-optimization even
gained you any performance, because the Java VM has a just-in-time
compiler that works under the assumption that you wrote plain java code.
If this JIT compiler recognizes standard for loops and optimizes them,
you might even loose performance.

The same goes for assumptions about a lot of other constructs, about
memory management, etc.

Those optimizations have one thing in common: They are not made because
a programm runs too slow. They are premature. Donald Knuth said "We
should forget about small efficiencies, say about 97% of the time:
premature optimization is the root of all evil."

So you should never waste any time on optimization unless you have made
tests, clearly identified the bottleneck, and are able to change your
design accordingly. I am sure it will almost never be the for loop you
have to optimize.


Back to the original topic: Tom posted two variants of looping through
an array:


| SLOW
|
| try {
| int i = 0;
| while(true)
| a[i++].f();
| } catch(ArrayIndexOutOfBoundsException e) {
| }
|
|
| FAST:
|
| for (int i = 0; i < a.length; i++)
| a.f();


If this is taken from the book "Effective Java", I don't understand the
point the author is trying to make. The assumption that using Exceptions
this way is _much_ slower than the standard way is wrong. I see that the
book might be older than the JVM JIT Compiler, but this just proves that
you shouldn't make too much assumptions when you are writing for a JVM
whose implementation might change. (BTW: this is a very poor example for
JIT testing, because it will probably never appear in real world code.)

What really irritated is that there was no mention of any other argument
why the slow version is a bad idea other than that it is slower. I
consider the slow version to be a violation of the concept of exceptions
because they are used for standard control flow. This outweighs any
performance issues by far, imho.


| Do you often judge books by second-hand reports of small sections of
| them?
|
| Just asking.

I really didn't want to judge the book without having read it myself.
But it still doesn't seems to be worth trying out from what I have see
until now.

It isn't unlikely that this was all taken out of context, or that the
book has much better information to offer on other topics. If you have
any valuable information about the book to add, I would appreciate to
hear about it.


Tim
 
T

Thomas Hawtin

Timo said:
This has been true in early versions of java.

And later versions. There are some specific cases were some JVMs will
optimise. You'd be foolish to rely on them. Perhaps for some reason the
next update release fails to inline one of your methods. Then your code
isn't just a little slow.

Here's a set of timings for Mike's code:

& /usr/java/jdk1.5.0_05/bin/java Timing
Slow time is 107630 milliseconds.
Fast time is 453 milliseconds.

Presumably the performance difference would be far worse if there were a
realistic number of frames on the stack.

I wouldn't want to be responsible for something like that to happen on a
live customer system.

Tom Hawtin
 
J

Jens Auer

Timo said:
Obviously not. Are you trolling? Here is an explanation for the interested:

A "micro-optimization" is a performance optimization at a low level. For
example, the following construct:

for( int i = upperbound; 0 != i--; ) {
// ...
}

is supposed to be more efficient than

for( int i = 0; i < upperbound; i++ ) {
// ...
}


This is actually true, the former construct can be more efficient in
statically compiled languages. But in a language like Java, which is
compiled into byte code and interpreted by a virtual machine that can
theoretically run on any platform, such optimizations don't make much
sense for several reasons:
If seen the former variant quite often in JDK source code (eg BitSet
implementation of and), but don't understand why this should be faster.
Both do the same number of instructions, with the difference of adding
and substraction. Can you explain why the first one is better to
optimize, or just give me a web-page explaining this?
 
T

tom fredriksen

Timo said:
A "micro-optimization" is a performance optimization at a low level. For
example, the following construct:

for( int i = upperbound; 0 != i--; ) {
// ...
}

is supposed to be more efficient than

for( int i = 0; i < upperbound; i++ ) {
// ...
}


This is actually true, the former construct can be more efficient in
statically compiled languages. But in a language like Java, which is
compiled into byte code and interpreted by a virtual machine that can
theoretically run on any platform, such optimizations don't make much
sense for several reasons:

Actually, this is not true. Any modern compiler optimises such details
away, in most cases both statements are compiled into the same
assembler/bytecode. What the statement really is, is syntactic sugar, or
lazy coding. Its quicker to write or its perceived as more "smart" than
other code. You choose... One should really stick to known and simple
idioms, so that it is easier to maintain the code by others and dont
increase the change of introducing bugs.

Btw, the correct syntax for this idiom is:

for ( int i = upperbound; i--; ) // i-- is false when it reaches 0

Back to the original topic: Tom posted two variants of looping through
an array:


| SLOW
|
| try {
| int i = 0;
| while(true)
| a[i++].f();
| } catch(ArrayIndexOutOfBoundsException e) {
| }
|
|
| FAST:
|
| for (int i = 0; i < a.length; i++)
| a.f();


If this is taken from the book "Effective Java", I don't understand the
point the author is trying to make. The assumption that using Exceptions
this way is _much_ slower than the standard way is wrong.


This was my point as well. the two code bits do not cause the SLOW code
to go 80 times slower, hence the original question. But as stated in
another post, creating an outer loop for these two statements would
increase overhead of them and then one would see the difference. So why
did he not include that in the book.
What really irritated is that there was no mention of any other argument
why the slow version is a bad idea other than that it is slower. I
consider the slow version to be a violation of the concept of exceptions
because they are used for standard control flow. This outweighs any
performance issues by far, imho.

He did mention that and so did I in the original post.

/tom
 
T

tom fredriksen

Mike said:
If you're timing the running of programs that do nothing but that code, you
won't see much difference, since the startup cost dwarfs everything else.

I only timed the two statements, what I did not do was add the outer
loop, since he did not mention it in the example. Thats what baffled me.
So in essence this is proof enough. It is creating the exceptions and
traversing the stack that takes time. But since exceptions should never
be used to control the flow of logic, the argument that "exceptions are
slow, dont use it as much" is really not the point then, and hence
ill-advised.

/tom
 
D

Daniel Dyer

If this is taken from the book "Effective Java", I don't understand the
point the author is trying to make. The assumption that using Exceptions
this way is _much_ slower than the standard way is wrong. I see that the
book might be older than the JVM JIT Compiler, but this just proves that
you shouldn't make too much assumptions when you are writing for a JVM
whose implementation might change. (BTW: this is a very poor example for
JIT testing, because it will probably never appear in real world code.)

What really irritated is that there was no mention of any other argument
why the slow version is a bad idea other than that it is slower. I
consider the slow version to be a violation of the concept of exceptions
because they are used for standard control flow. This outweighs any
performance issues by far, imho.

This is exactly the point that Bloch is making in Effective Java. The two
examples come from "Item 39: Use exceptions only for exceptional
conditions" and the first is preceeded by the comment "Horrible abuse of
exceptions. Don't ever do this!". The discussion of performance is just
a rebuttal of the argument that using exceptions in this way would be
quicker by avoiding termination test for each iteration of the loop.
| Do you often judge books by second-hand reports of small sections of
| them?
|
| Just asking.

I really didn't want to judge the book without having read it myself.
But it still doesn't seems to be worth trying out from what I have see
until now.

I agree with Mike's assessment of the book. And it's not just the two of
us, it is widely regarded as the best book ever written on Java
programming. There are a few tips, such as the type-safe enum pattern,
that are obsoleted by Java 5 features, but it is still well worth reading.
It isn't unlikely that this was all taken out of context, or that the
book has much better information to offer on other topics. If you have
any valuable information about the book to add, I would appreciate to
hear about it.

Check-out the reviews on Amazon, or search the Google Groups archive for
mentions of it on this group.

Dan.
 
R

Roedy Green

If seen the former variant quite often in JDK source code (eg BitSet
implementation of and), but don't understand why this should be faster.
Both do the same number of instructions, with the difference of adding
and substraction. Can you explain why the first one is better to
optimize, or just give me a web-page explaining this?

The short answer is computers have shortcuts to compare with 0 that
are faster than comparing with N. When you go down in a for loop,
under the hood, you determine loop termination by comparing the index
with 0, and with N when going up.

Up loops are particularly painful if N gets recomputed each time round
the loop, e.g. ArrayList.size(). Happily 0, the termination value,
when you work down never get recomputed.

To explain a little more deeply, here is a quick course in Intel
assembler.

[add-register] is a single instruction that adds two 32-bit registers
a and b and leaves the result in a. As a side effect it sets a magic
register called the condition code that records whether the result was
positive, negative, zero, overflowed etc. You get this information
for free as a side effect of an arithmetic instruction.

So if you said something like this in Java,

boolean biggerThanZero = ( a - b ) > 0;

you can in a sense, you do the whole thing in one machine instruction,
[sub-register] that takes only one clock cycle.

if you wrote

if ( ( a - b ) > 0 ) // or almost equivalently if ( a > b )
{
dothis();
}
else
{
dothat();
}


the if part compiles to two machine instructions like this:
[subtract] a, b
[jump if result <= 0] to else code

Typically the conditional jump is much faster when it falls through to
the if code than when it jumps to jump to the else clause. It barrels
along where it was planning to go anyway, rather than taking a detour,
throwing a monkey wrench into its plans for the future. The
conditional jump just interrogates the pre-computed magic condition
code register which we got free as a side effect of the subtract.

Now lets have a look at the code in an i++ style loop.

I am explaining the principle, not the Intel architecture, so I feel
free to lie slightly for the sake of simplicity.

i++ is done with an [increment] instruction that adds 1 in a single
cycle.

Then you do a compare instruction with N, which sets the condition
code. A compare is like a subtract, except you discard the result and
it works even when there is overflow, and it also provides both signed
and unsigned comparison results in the condition code register. Then
you do a conditional jump out of the loop. Otherwise you fall though
and execute the loop body one more time.

So, for looping UP a total 3 instructions, with a nice fall-through on
the conditional jump in the more common case.

Now lets say you count down. You subtract 1. As a side effect you get
the condition code telling you if the result is +ve, 0 or -ve.
You do a conditional jump out of the loop if it has gone negative.

So, for looping DOWN, a total of 2 instructions with a nice
fall-through on the usual case conditional jump.

People familiar with Intel assembler will chime that the [decrement]
instruction fails to set the condition code. You can subtract one, in
one cycle too, not using the [decrement] instruction, using [subtract]
with a register containing a convenient one, or subtract literal 1,
which DOES set the condition code. (Much of my dicey coding in 8086
assembler for the BBL Forth engine revolved around always having a
convenient 1 sitting in a register somewhere to use for a
condition-code setting increment or decrement.)

This one cycle overhead penalty for upward counted loops is only going
to matter on a very short loop body. Further it could be offset by
preemptive RAM caching hardware that presumes you generally work up.

Other factors in loop optimisation: If the loop body is small enough,
it may be cached totally on the CPU chip greatly increasing its speed.
So oddly, it sometimes pays to break a loop up in to two shorter
passes, running over all the elements twice. This is all complicated
immensely by how ram for operands and code is cached and preemptively
cached. Nowadays all you can do is code it several ways and see which
works faster. Jet, an AOT compiler, does versioning, creating multiple
loop bodies, each with a different set of presumptions. It decides at
the top of the loop which body to use, so it does not have to test
conditions repeatedly as it goes through the loop body.


E.g. Jet would convert code like this:

for ( i=0; i<n; i++ )
{
if ( i mod 2 != 0 )
{
dothing1();
}
else
{
dothing2();
}
sum += i;
if ( i mod 2 != 0 )
{
dothing3();
}
else
{
dothing4();
}
}

to code like this:


for ( i=0; i<n; i++ )
{
if ( i & 1 != 0)
{
dothing1();
sum += i;
do1thing3();
}
else
{
dothing2();
sum += i;
dothing4();
}
}

or maybe even code like this if side effects can be proved the same.

for ( i=1; i<n; i+=2; )
{
dothing1();
sum += i;
do1thing3();
}
for ( i=0; i<n; i+=2 )
{
dothing2();
sum += i;
dothing4();
}
}
 
R

Roedy Green

for ( int i = upperbound; i--; ) // i-- is false when it reaches 0

You are thinking in C. i-- does not evaluate to a boolean, and
further you continue the loop one more time when i==0. You stop only
when it goes negative.

I think this it what you meant to say

for ( int i = n; --i >= 0; )
or
for ( int i =n-1; i >= 0; i-- )

The problem is programmers are not familiar with these idiom and won't
be able to tell easily what the actual range of indexes is and where
the code comes out in the wash when n= 0.

It is not immediately obvious you are iterating n-1 .. 0 as it is when
you use the canonical familiar form:

for ( int i = 0 ; i<n; i++)
 
T

Thomas Hawtin

tom said:
Btw, the correct syntax for this idiom is:

for ( int i = upperbound; i--; ) // i-- is false when it reaches 0

That isn't even legal Java.



There is a bug in Sun's HotSpot that tends to make downward loops slower.

Tom Hawitn
 
J

Jens Auer

Roedy Green wrote:
.... Some very nice explaination

Thank you for this informative post. I immediately made a little test
program out of the previously posted code to test the loop-variants:
class Timing
{
public static void f1(int[] a) {
for (int i=1000000; 0 != i--;)
for (int j=9; 0 <= j; --j)
a[j] = j;
}

public static void f2(int[] a) {
for (int i = 0; i != 1000000; ++i)
{
for (int j = 0; j != 10; ++j)
{
a[j] = j;
}
}
}

public static void main(String[] args) throws Exception
{
int[] a = new int[10];
final int runs = Integer.valueOf(args[0]);

long start = System.currentTimeMillis();
for (int i=0; i != runs; i++)
f1(a);
System.out.println(
"Slow time is " + (System.currentTimeMillis() - start) +
" milliseconds.");

start = System.currentTimeMillis();
for (int i=0; i != runs; i++)
f2(a);
System.out.println(
"Fast time is " + (System.currentTimeMillis() - start) +
" milliseconds.");
}
}

With the normal JDK 5.0 virtual machine, both loops seem to perform more
or less equally fast:
auer@lsi-08:~/tmp$ java Timing 1
Slow time is 32 milliseconds.
Fast time is 34 milliseconds.
auer@lsi-08:~/tmp$ java Timing 100
Slow time is 3047 milliseconds.
Fast time is 3025 milliseconds.
auer@lsi-08:~/tmp$ java Timing 1000
Slow time is 30072 milliseconds.
Fast time is 30206 milliseconds.

But using the -server jit compiler, the incrementing loop outperforms
the decrementing:
auer@lsi-08:~/tmp$ java -server Timing 1
Slow time is 38 milliseconds.
Fast time is 42 milliseconds.
auer@lsi-08:~/tmp$ java -server Timing 100
Slow time is 1636 milliseconds.
Fast time is 1078 milliseconds.
auer@lsi-08:~/tmp$ java -server Timing 1000
Slow time is 14821 milliseconds.
Fast time is 10360 milliseconds.

Measuring is done with Sun JDK 5.0:
java version "1.5.0_06"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_06-b05)
Java HotSpot(TM) Client VM (build 1.5.0_06-b05, mixed mode, sharing)

It seems that the Sun JIT is more optimized to incrementing case, but I
don't see nay documentation which optimization the JIT does and when
they are applied, which I find very annoying.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,764
Messages
2,569,567
Members
45,042
Latest member
icassiem

Latest Threads

Top