[bind10-dev] whether/when to use exceptions

Shane Kerr shane at isc.org
Wed Oct 14 11:30:15 UTC 2009


All,

On Fri, 2009-10-09 at 11:27 -0700, JINMEI Tatuya / 神明達哉 wrote:
> At Fri, 9 Oct 2009 00:32:03 -0400,
> christos at zoulas.com (Christos Zoulas) wrote:
> 
> > | Whether or not we use our own exceptions heavily, we should also note
> > | that it's (very) difficult to make the code completely exception-free,
> > | especially if we want to use STL (and I think we do) because we should
> > | also expect a std::bad_alloc exception.  So, I suspect we'll need to
> > | have some try-catch anyway.
> > 
> > It depends what are your goals. The STL code is very uneven; some of it
> > is pretty good, but there are parts that are really bad (the hashtable
> > is a red-black tree internally, and the default string hashing function
> > is poor for example). It also does excessive allocations and de-allocations.
> > 
> > Strings are not great either... If you want a high performance application,
> > you'll probably want to avoid STL. If you don't care about burning CPU,
> > STL is fine.
> > 
> > The latest example was one someone @ work wrote a feed handler using STL
> > that used ~60% cpu, and when we ripped out all the STL code and used plain
> > c stuff, it used < 10%... Again this was an application where STL did
> > excessive allocation/deallocation and this really hurt performance.
> 
> Hmm...if we really decide to eliminate both exceptions (completely)
> and STL, I guess we'd lose much of the original motivation of (my
> understanding of) why we chose C++ in the first place.

Indeed.

> As for the efficiency concern about STL, I see it.  I've not fully
> considered the performance implication in my initial experimental
> work, but I was still aware of it.  My initial considerations on this
> point are:
> 
> - performance doesn't matter much for the DNS name/RR/message API as a
>   general purpose library.  I guess most of the users will be happy
>   with a moderate level of performance (e.g., they wouldn't have to
>   handle 100,000qps), and clarity and convenience would be more
>   important.  I believe STL helps here.
> 
> - for performance-sensitive applications such as
>   authoritative/recursive DNS servers that would be included in the
>   base BIND10 package, we'll still use the same interface but
>   implement it in a more efficient way internally.  For example, while
>   the user of the general API may use the generic "Name" class, which
>   may internally uses std::string, the internal server version would
>   represent a DNS name as a bare char * string which is located
>   somewhere in the on-memory database.  Also, we could probably avoid 
>   allocation/deallocation overhead with STL (in some limited optimized
>   cases) by pre-reserving a reasonably sufficient space and reusing the
>   same STL container as much as possible (and storing pointer or
>   pointer-like objects in the container, rather than a copy of a real
>   object)

Given that PowerDNS is written in C++, using exceptions, and benchmarks
faster than BIND 9 under some circumstances, I would say that exceptions
do not necessarily lead to slow code. Both compiler writers and CPU
designers have spent the best part of the last 20 years optimizing code
to improve performance of languages with exceptions (which is everything
but C and lua, apparently). 

Most programs have a very small portion of code that is time-critical.
If exceptions have a high cost, then they can be removed for these
areas. I doubt this is necessary, but this is only a guess.

I note that there is an action point on someone from the last BIND 10
call to benchmark exceptions. As we say in Dutch, "meten is weten" -
roughly "measuring is knowing" (*)



As for why we want to use exceptions... there are two reasons I think. 

The first is that with exceptions you move error handling out of the
main flow of code.

An example:
        
        fp = fopen("foo.txt", "w");
        if (fp == NULL) {
            return ERROR_CODE;
        }
        for (i=0; i<10; i++) {
            if (fprintf(fp, "%d\n", i) < 0) {
                fclose(fp);
                remove("foo.txt");
                return ERROR_CODE;
            }
        }
        if (fclose(fp) != 0) {
            remove("foo.txt");
            return ERROR_CODE;
        }
        return SUCCESS_CODE;

Contrast with:

        try:
            fp = open("bar.txt", "w")
            for i in range(10):
                fp.write("%d\n" % I)
            fp.close()
            return SUCCESS_CODE
        except IOError, e:
            if fp:
                fp.close()
            os.remove("bar.txt")
            return ERROR_CODE

The important thing to note here is that the error handling in the
second snippet is all in one section. When you read well-written C code,
half of the program is error handling. Which is fine, but the problem is
that the error handling sits in the middle of the actual logic of what
is going on. So you typically have to read a function two or more times.
The first time to figure out what is the actual algorithm and what is
error handling, the second time to follow the logic, and perhaps a third
time to check the error handling.

Easier to read code means less bugs, and reduced maintenance time, and
easier refactoring and extension later.


The second reason exceptions are good is for fault isolation. As Michael
mentions (I think it was Michael), in C you have to check for errors
everywhere and pass them up the stack if you don't know how to handle
this. In many cases, it is quite difficult to know what to do on error.
In a language with exceptions, one can simply shrug and let the
exception go further up the call stack.

An important area that we can do much better with if we have exceptions
are coder errors. By this I basically mean assert() failures. With
exceptions these become an exception rather than an abnormal exit, and
some action may be taken.

Of course, it is difficult to write code that handles coder errors.
Rather than having a blanket policy, one must review each piece of
software in context and decide what the best thing to do is.

For example, if we get an update message that puts the system in an
unexpected state, we may be able to simply drop the update and continue
on. However, if we discover the problem after we have started the
update, then we may have to restart the process doing the update. In
extreme cases we may have corrupted our database - although using an SQL
database that is ACID can make this very unlikely.


Exceptions are a good thing. The question must be "when to use
exceptions", not "whether to use exceptions".

IMHO, as always. ;)

--
Shane

(*) I've also seen the following quote:
        	"Accurate measurement is the beginning of all wisdom."
                	               - Imhotep 2650 B.C
        Sadly Google doesn't find me an original source, but I vaguely
        recall writing it on a whiteboard 15 years ago after reading it
        somewhere. Maybe an Egyptologist reading the list can point us
        to the appropriate hieroglyphs. :)




More information about the bind10-dev mailing list