Re: abusing exceptions
In article <4758B9F5.7050604@day-one.com>, clcppm-poster@this.is.invalid
says...
[ ... ]
i was wondering about using use exception handling as a non local goto
so i can implement tail calls, but the result is very slow with msvc
7.1, release build.
That's to be expected -- since exceptions are intended for handling only
_exceptional_ situations, compiler writers don't normally attempt to
make them fast at all. Rather the contrary, their whole emphasis is
generally on optimizing everything else, and if doing so makes exception
handling slower still, so be it.
can anybody tell me why, whether this is a blind alley, and how to speed
things up if it isn't?
It's pretty much a blind alley. The obvious way to speed up the code you
posted would be something like:
for (int i=0; i<1000; i++)
std::cout << i << ' ';
but I'm pretty sure that's not what you're really after. :-) As far as a
faster way of implementing tail-calls, the next possibility would be to
use setmjmp/longjmp. The problem with these is that they don't guarantee
that destructors are invoked upon exit from a scope (which exceptions
do). Under the circumstances, you can probably get away with that, and
the result will usually be a bit faster. I did a quick hack on your code
to convert it to use setjmp/longjmp, added a bit of instrumentation, and
so on, to get this:
#include <iostream>
#include <setjmp.h>
#include <time.h>
enum{SZ=10000000}; // increased to get a measurable time
using std::ostream;
using std::cout;
ostream &s_tail_call(jmp_buf buf, ostream &os, int &i) {
// eliminated output for timing purposes
// os << i++ << ' ';
i++;
if(i < SZ) {
longjmp(buf, i);
}
else {
longjmp(buf, -1);
}
return os;
}
ostream &s_tail_call_0(ostream &os) {
int i = 0;
jmp_buf buf;
while(-1!=(i=setjmp(buf)))
s_tail_call(buf, os, i);
return os;
}
struct Continuation
{
Continuation(int cont)
:cont_(cont)
{}
int cont_;
};
ostream &tail_call(ostream &os, int &i)
{
// likewise, eliminated otuput for timing purposes
// os << i++ << ' ';
i++;
if(i < SZ)
{
throw Continuation(i);
}
else
{
// do nothing
}
return os;
}
ostream &tail_call_0(ostream &os)
{
int i = 0;
while(true)
{
try
{
tail_call(os, i);
}
catch(Continuation c)
{
i = c.cont_;
continue;
}
break;
}
return os;
}
// simple class to get execution time of a function in seconds.
template<class T>
class timer {
ostream & x;
T *f;
public:
timer(T *t, ostream &os) : x(os), f(t) {}
double operator()() {
clock_t start = clock();
f(x);
clock_t stop = clock();
return (stop - start)/(double)CLOCKS_PER_SEC;
}
};
// function-template front-end to allow automatic deduction of the type
// for the timer class.
template<class T>
double do_time(T *t, ostream &os) {
timer<T> x(t, os);
return x();
}
int main(int argc, char* argv[])
{
std::cout << "Using setjmp: "
<< do_time(s_tail_call_0, cout)
<< std::endl;
std::cout << "Using Exceptions: "
<< do_time(tail_call_0, cout)
<< std::endl;
return 0;
}
On my machine, the result is:
Using setjmp: 0.328
Using Exceptions: 17.234
IOW, setjmp/longjmp is about 50 times faster. Just keep in mind that the
speed increase is largely because setjmp/longjmp and exception handling
don't do entirely the same things. For this example, setjmp and longjmp
work fine -- but in the wrong situation, the results may not be nearly
so pretty...
--
Later,
Jerry.
The universe is a figment of its own imagination.
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]