Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

What's your cost of throwing an exception ?

80 views
Skip to first unread message

Bonita Montero

unread,
Apr 20, 2023, 9:34:04 PM4/20/23
to
#include <iostream>
#include <exception>
#include <chrono>

using namespace std;
using namespace chrono;

void throwExc();

int main()
{
constexpr size_t
ROUNDS = 100,
N_EXC = 1'000;
using dur_t = high_resolution_clock::duration;
dur_t tMin( dur_t::max() );
for( size_t r = ROUNDS; r--; )
{
auto start = high_resolution_clock::now();
for( size_t e = N_EXC; e--; )
try
{
throwExc();
}
catch( exception const & )
{
}
dur_t t = high_resolution_clock::now() - start;
tMin = t < tMin ? t : tMin;
}
cout << (double)tMin.count() / N_EXC << endl;
}

#if defined(_MSC_VER)
#elif defined(__GNUC__) || defined(__clang__)
__attribute__((noinline))
#endif
void throwExc()
{
throw exception();
}

On my Linux-PC throwing an exception is about 1200ns.

wij

unread,
Apr 21, 2023, 7:53:24 AM4/21/23
to
//-------------------------------------------------------------------------------------------
#include <Wy.stdio.h>
#include <Wy.time.h>

using namespace Wy;

int ret_int()
{ return 3; };

Errno ret_errno()
{ return 3; };

void throw_int()
{ throw (int)3; };

const int Loops=40000000;

Timespec t0()
{
Timespec tm0,tm1;
tm0=Wy::now(CLOCK_THREAD_CPUTIME_ID);
for(int i=Loops; i; --i) {
int r=ret_int(); // Compiler warning: not used (Ok, meaningful)
r=ret_int();
r=ret_int();
r=ret_int();
r=ret_int();
r=ret_int();
r=ret_int();
r=ret_int();
r=ret_int();
r=ret_int();
}
tm1=Wy::now(CLOCK_THREAD_CPUTIME_ID);
return tm1-tm0;
};

Timespec t1()
{
Timespec tm0,tm1;
tm0=Wy::now(CLOCK_THREAD_CPUTIME_ID);
for(int i=Loops; i; --i) {
Errno r=ret_errno();
r=ret_errno();
r=ret_errno();
r=ret_errno();
r=ret_errno();
r=ret_errno();
r=ret_errno();
r=ret_errno();
r=ret_errno();
r=ret_errno();
}
tm1=Wy::now(CLOCK_THREAD_CPUTIME_ID);
return tm1-tm0;
};

Timespec t2()
{
Timespec tm0,tm1;
const int ScaleDown=100;

tm0=Wy::now(CLOCK_THREAD_CPUTIME_ID);
for(int i=Loops/ScaleDown; i; --i) {
int r; // Compiler warning: not used (Ok, meaningful)
try { throw_int(); } catch(int e) { r=e; };
try { throw_int(); } catch(int e) { r=e; };
try { throw_int(); } catch(int e) { r=e; };
try { throw_int(); } catch(int e) { r=e; };
try { throw_int(); } catch(int e) { r=e; };
try { throw_int(); } catch(int e) { r=e; };
try { throw_int(); } catch(int e) { r=e; };
try { throw_int(); } catch(int e) { r=e; };
try { throw_int(); } catch(int e) { r=e; };
try { throw_int(); } catch(int e) { r=e; };
}
tm1=Wy::now(CLOCK_THREAD_CPUTIME_ID);

{// Compute ScaleDown*(tm1-tm0)
Errno r;
tm1-=tm0;
const VLInt<unsigned int> Giga(Giga.itv(1000000000ul));
VLInt<unsigned int> a,b;
a=a.itv(static_cast<uint64_t>(tm1.second()));
a*=Giga;
a+=a.itv(static_cast<unsigned long>(tm1.nano()));
a*=a.itv(static_cast<unsigned int>(ScaleDown));
r=a.div(Giga,b);
if(r!=Ok) {
WY_THROW( Reply(r) );
}
if((a.num_digits()!=1)||(b.num_digits()!=1)) {
WY_THROW( Reply(r) );
}
if((r=tm1.reset(a._arr().front(),b._arr().front()))!=Ok) {
WY_THROW( Reply(r) );
}
}
return tm1;
//return ScaleDown*(tm1-tm0);
};

int main(int argc, char* argv[])
try {
cout << "Measuring...." WY_ENDL;
const Timespec tm0= t0();
const Timespec tm1= t1();
const Timespec tm2= t2();

cout << "Return int = " << tm0 << WY_ENDL
<< "Return Errno = " << tm1 << WY_ENDL
<< "Throw int = " << tm2 << WY_ENDL
;

cout << "OK" WY_ENDL;
return 0;
}
catch(const Errno& e) {
cerr << wrd(e) << WY_ENDL;
return e.c_errno();
}
catch(...) {
cerr << "Unknown throw type" WY_ENDL;
throw;
};
//----------------------------------------------------------------------

[]$ ./sp_throw
Measuring....
Return int = 0.595733589
Return Errno = 1.039364795
Throw int = 539.335618700
OK
------------- (compiled with -O2)
[]$ ./sp_throw
Measuring....
Return int = 0.482806556
Return Errno = 0.519995772
Throw int = 556.814217900
OK
//------------------------------------------------------------------------

Throwing int is approximately 1000 times slower than returning int.
IIRC, the time ratio is not changed (nearly 20 years).

Öö Tiib

unread,
Apr 21, 2023, 8:04:40 AM4/21/23
to
On Friday, 21 April 2023 at 04:34:04 UTC+3, Bonita Montero wrote:
>
> On my Linux-PC throwing an exception is about 1200ns.

Current exceptions in C++ are better overall performance when these
happen less frequently that once in thousands of runs of operation. Even
then these may be unacceptable if failed and succeeded run must
perform at similar speed. Additional price of exceptions is bigger binary
of code doing stack unwinding and RTTI of exceptions at catch sites.

Both performance and usage of exceptions would be of course different
if the Swift-like (or Herb Sutter-sponsored) exceptions were introduced
into C++. It is yet at stage of idea, so does not matter.

Bonita Montero

unread,
Apr 21, 2023, 8:13:47 AM4/21/23
to
Am 21.04.2023 um 13:53 schrieb wij:

> Throwing int is approximately 1000 times slower than returning int.

You don't need such a large program for that. The loop-unrolling
is useless here, especially for exceptions where the process of
throwing an exception is that heavy-weight. And loop-unrolling
works much simpler with a generic lambda with a function-object
and a index_sequence as an argument. And the ret_int/ret_errno
-code is automatically inlined for sure; you would need to make
these functions __declspec(noinline) or __attribute((noinline)).
And it isobvious that returning an int is fast so there's no need
to measure that.

Throwing an exception is extremely rare, usually not at all. Excep-
tions are made for these cases because the case of not throwing an
exception costs next to nothing with modern implementations. In the
case of a return value, this must be evaluated at all levels of the
call graph and, if necessary, translated for the higher-level caller.
This does not apply to exceptions.

> IIRC, the time ratio is not changed (nearly 20 years).

I just wrote my small program to show that throwing an exception is
slow because Muttley@... thought that exceptions would be an alter-
native to returning an error to the immediate caller of a function.
This is obvious for most but Muttley@... missed that in his response
to me.

Bonita Montero

unread,
Apr 21, 2023, 8:15:21 AM4/21/23
to
Am 21.04.2023 um 14:04 schrieb Öö Tiib:

> Both performance and usage of exceptions would be of course different
> if the Swift-like (or Herb Sutter-sponsored) exceptions were introduced
> into C++. It is yet at stage of idea, so does not matter.

wij's code is a mess but the measurments of throwing an exceptions
are that what you can expect from today's implementations.

Bonita Montero

unread,
Apr 21, 2023, 8:22:46 AM4/21/23
to
Am 21.04.2023 um 14:15 schrieb Bonita Montero:

> ... And loop-unrolling works much simpler with a generic lambda
> with a function-object and a index_sequence as an argument. ...

Try this:

#include <iostream>
#include <utility>

using namespace std;

int main()
{
auto unroll = []<size_t ... Indices, typename Fn >(
index_sequence<Indices ...>, Fn fn )
{
(fn.template operator ()<Indices>(), ...);
};
unroll( make_index_sequence<10>(), []<size_t I>() { cout << I << endl; } );
}

I've an own header unroll.h:

#pragma once
#include <utility>
#include <concepts>
#include <iterator>

#if defined(_MSC_VER)
#define UNROLL_FORCEINLINE __forceinline
#elif defined(__GNUC__) || defined(__clang__)
#define UNROLL_FORCEINLINE __attribute__((always_inline))
#else
#define UNROLL_FORCEINLINE inline
#endif

template<size_t N, typename Fn>
requires (N >= 1) && requires( Fn fn ) { { fn.template operator
()<(size_t)N - 1>() } -> std::same_as<void>; }
UNROLL_FORCEINLINE void unroll( Fn fn )
{
auto unroll_n = [&]<size_t ... Indices>( std::index_sequence<Indices ...> )
{
(fn.template operator ()<Indices>(), ...);
};
unroll_n( std::make_index_sequence<N>() );
}

template<size_t N, bool Unroll, typename Fn>
requires (N >= 1) && requires( Fn fn ) { { fn() } -> std::same_as<void>; }
UNROLL_FORCEINLINE void unroll( Fn fn )
{
if constexpr( Unroll )
{
auto unroll_n = [&]<size_t ... Indices>( std::index_sequence<Indices
...> )
{
return ((Indices, fn()), ...);
};
unroll_n( std::make_index_sequence<N>() );
}
else
for( size_t i = 0; i != N; fn(), ++i );
}

template<size_t N, typename Fn>
requires (N >= 1) && requires( Fn fn ) { { fn.template operator
()<(size_t)N - 1>() } -> std::convertible_to<bool>; }
UNROLL_FORCEINLINE bool unroll( Fn fn )
{
auto unroll_n = [&]<size_t ... Indices>( std::index_sequence<Indices
...> ) -> bool
{
return (fn.template operator ()<Indices>() && ...);
};
return unroll_n( std::make_index_sequence<N>() );
}

template<size_t N, bool Unroll, typename Fn>
requires (N >= 1) && requires( Fn fn ) { { fn() } ->
std::convertible_to<bool>; }
UNROLL_FORCEINLINE bool unroll( Fn fn )
{
if constexpr( Unroll )
{
auto unroll_n = [&]<size_t ... Indices>( std::index_sequence<Indices
...> ) -> bool
{
return ((Indices, fn()) && ...);
};
return unroll_n( std::make_index_sequence<N>() );
}
else
{
for( size_t i = 0; i != N; ++i )
if( !fn() )
return false;
return true;
}
}

template<std::size_t N, typename RandomIt, typename UnaryFunction>
requires std::random_access_iterator<RandomIt>
&& requires( UnaryFunction fn, std::iter_value_t<RandomIt> elem ) { {
fn.template operator()<(size_t)-1>( elem ) } -> std::same_as<void>; }
UNROLL_FORCEINLINE RandomIt unroll_for_each( RandomIt begin, RandomIt
end, UnaryFunction fn )
{
RandomIt &it = begin;
if constexpr( N > 1 )
for( ; it + N <= end; it += N )
unroll<N>( [&]<size_t I>() { fn.template operator()<I>( it[I] ); } );
for( ; it < end; ++it )
fn.template operator ()<(size_t)-1>(*begin);
return it;
}

template<std::size_t N, typename RandomIt, typename UnaryFunction>
requires std::random_access_iterator<RandomIt>
&& requires( UnaryFunction fn, std::iter_value_t<RandomIt> elem ) { {
fn.template operator()<(size_t)-1>( elem ) } -> std::same_as<bool>; }
UNROLL_FORCEINLINE RandomIt unroll_for_each( RandomIt begin, RandomIt
end, UnaryFunction fn )
{
RandomIt &it = begin;
if constexpr( N > 1 )
for( ; it + N <= end && unroll<N>( [&]<size_t I>() { return
fn.template operator ()<I>( it[I] ); } ); it += N );
for( ; it < end; ++it )
fn.template operator ()<(size_t)-1>(*begin);
return it;
}

With the last function and some additional tricks
I improved Fletcher's hash on my computer by * 400%.

Mut...@dastardlyhq.com

unread,
Apr 21, 2023, 9:46:14 AM4/21/23
to
No muttley didn't think that. Muttley was pointing out that when using some
std::optional functions they can throw an error so if you have to cope with
exceptions they might as well be your own.

Yet again you didn't understand an obvious point.

Bonita Montero

unread,
Apr 21, 2023, 1:28:54 PM4/21/23
to
Am 21.04.2023 um 15:45 schrieb Mut...@dastardlyhq.com:

> No muttley didn't think that. ...

You suggested to use exceptions for return-values.

Mr Flibble

unread,
Apr 21, 2023, 4:30:38 PM4/21/23
to
The cost of throwing an exception is not important as exceptions are
supposed to be rare (exceptional - the clue is in the name) events; what
is important is the cost of NOT throwing an exception: any decent C++
exception implementation will have zero cost (time domain) for the code
path where the exception is NOT thrown.

/Flibble

Bonita Montero

unread,
Apr 21, 2023, 10:38:09 PM4/21/23
to
Am 21.04.2023 um 22:30 schrieb Mr Flibble:

> The cost of throwing an exception is not important as exceptions are
> supposed to be rare (exceptional - the clue is in the name) events; ...

I know that, but Muttley@... suggested in another posting always
to use exceptions instead of error return values. I've shown that
this doesn't make sense.



Mr Flibble

unread,
Apr 22, 2023, 12:44:53 AM4/22/23
to
Wrong. Always using exceptions instead of ERROR return values is
perfectly fine because ERRORS, like exceptions, should be rare
(exceptional) events.

/Flibble

Bonita Montero

unread,
Apr 22, 2023, 2:14:24 AM4/22/23
to
Am 22.04.2023 um 06:44 schrieb Mr Flibble:

> Wrong. Always using exceptions instead of ERROR return values is
> perfectly fine because ERRORS, like exceptions, should be rare
> (exceptional) events.

Look at how Java uses exceptions where in C++ you'd use return-values.
That's while exceptions in Java are magnitudes faster (the language
itsef aint't faster). If you would do that in C++, like Muttley sug-
gested, you'd sometimes have performance problems.

Mut...@dastardlyhq.com

unread,
Apr 22, 2023, 5:19:05 AM4/22/23
to
No, I suggested them for errors. Learn to read.

Bonita Montero

unread,
Apr 22, 2023, 7:11:55 AM4/22/23
to
Am 22.04.2023 um 11:18 schrieb Mut...@dastardlyhq.com:

> No, I suggested them for errors. Learn to read.

That's what I said or didn't mean, that using exceptions for
return values should be obvious anyway. But using exceptions
to inform the immediate caller doesn't make sense.

Chris M. Thomasson

unread,
Apr 22, 2023, 4:21:31 PM4/22/23
to
Would you want to put a bullet in your brain if the company that just
hired you, showed their coding and function handling style that looked
something akin to the following:

// Typing this in the newsreader sorry for any typos...
__________________________________
struct ct_func_input
{
double m_angle;
};


struct ct_func_output
{
unsigned long m_status;
double m_sin;
double m_cos;
};


// a contrived condition: ;^)
// input.m_angle must not be greater than pi/2

// return:
// output.m_status == 0 == success
// output.m_status == 1 == failure

ct_func_output
ct_func(
ct_func_input const& input
) {
// check condition
if (input.m_angle > M_PI / 2)
{
// ERROR on the condition...
return { 1, 0 };
}

return {
0,
std::sin(input.m_angle),
std::cos(input.m_angle)
};
}
__________________________________



Some code that calls this ct_func:
__________________________________
double sum_sin = 0;
double sum_cos = 0;

// call the function ct_func with valid data...
ct_func_output output = ct_func({0, 1.5});

// check the output status...
if (output.m_status != 0)
{
// OH SHIT! We have an error... ;^o
// log error
return;
}

// Okay! We can use the result.
// Lets add to a sum, for fun...
sum_cos = sum_cos + output.m_cos;
sum_sin = sum_sin + output.m_sin;
__________________________________



Well, does it make you want to quit? lol. ;^)

Mut...@dastardlyhq.com

unread,
Apr 24, 2023, 4:34:53 AM4/24/23
to
On Sat, 22 Apr 2023 13:21:12 -0700
"Chris M. Thomasson" <chris.m.t...@gmail.com> wrote:
>Well, does it make you want to quit? lol. ;^)

Depends how much they were paying :)

Chris M. Thomasson

unread,
Apr 24, 2023, 6:11:34 PM4/24/23
to
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
God damn typo! ct_func takes a ct_func_input:

> struct ct_func_input
> {
> double m_angle;
> };

Well, let me fix that:

ct_func_output output = ct_func({1.5});

Sorry everybody! ;^o

Chris M. Thomasson

unread,
Apr 24, 2023, 6:15:35 PM4/24/23
to
You take a look at the style, and say at least $100,000 a year... ;^)

Mut...@dastardlyhq.com

unread,
Apr 25, 2023, 4:31:10 AM4/25/23
to
On Mon, 24 Apr 2023 15:15:18 -0700
"Chris M. Thomasson" <chris.m.t...@gmail.com> wrote:
>On 4/24/2023 1:34 AM, Mut...@dastardlyhq.com wrote:
>> On Sat, 22 Apr 2023 13:21:12 -0700
>> "Chris M. Thomasson" <chris.m.t...@gmail.com> wrote:
>>> Well, does it make you want to quit? lol. ;^)
>>
>> Depends how much they were paying :)
>>
>
>You take a look at the style, and say at least $100,000 a year... ;^)

I'll take that sort of style over the pointlessly multithreaded nonsense
I've seen over the years written by people who can barely spell "race
condition" never mind understand how not to cause them in the first place.

0 new messages