0

According to Does try-catch block decrease performance, I understand that if we wrap some rarely throw codes into a try catch block, there is almost no cost, since compilers tend to optimize for the normal path. However, if an exception should throw, there will be a larger penalty.

I write the following codes to verify this

#include <iostream>
#include <stdexcept>
#include <exception>
#include <thread>
#include <chrono>
#include <cstdio>
#include <vector>

using std::chrono::high_resolution_clock;
using std::chrono::duration_cast;
using std::chrono::duration;
using std::chrono::milliseconds;

std::vector<int> global_vec;

int exception_call_all_throw_inner(int a) {
    throw std::runtime_error("throw");
    return 1;
}

int exception_all_throw_call(int & a) {
    try
    {
        exception_call_all_throw_inner(a);
    } catch (...) {
        a = (a + 1) % 10000;
        if (a % 100 == 0) {
            global_vec.push_back(a);
        }
        if (a == 9999) {
            global_vec.clear();
        }
        if (a % 100 == 55) {
            a += global_vec.size();
        }
    }
    return a;
}

int exception_call_no_throw_inner(int a) {
    if (a == 10010) {
        // Which will never happen
        throw std::runtime_error("prevent optimization");
    }
    return 1;
}

int exception_no_throw_call(int & a) {
    a = (a + 1) % 10000;
    if (a % 100 == 0) {
        global_vec.push_back(a);
    }
    if (a == 9999) {
        global_vec.clear();
    }
    try
    {
        exception_call_no_throw_inner(a);
    } catch (...) {
        printf("error");
    }
    if (a % 100 == 55) {
        a += global_vec.size();
    }
    return a;
}

int no_exception_call(int & a) {
    a = (a + 1) % 10000;
    if (a % 100 == 0) {
        global_vec.push_back(a);
    }
    if (a == 9999) {
        global_vec.clear();
    }
    if (a % 100 == 55) {
        a += global_vec.size();
    }
    return a;
}

template<typename F>
int loop_main(F f) {
    int a = 1;
    global_vec.clear();
    for(int i = 0; i < 19999933; i++) {
        f(a);
    }
    printf("%d\n", a);
    return 0;
}

int main() {
    int ms_inte_no;
    int ms_inte_all;
    int ms_intnoe;
    {
        auto t1 = high_resolution_clock::now();
        loop_main(exception_no_throw_call);
        auto t2 = high_resolution_clock::now();
        ms_inte_no = duration_cast<milliseconds>(t2 - t1).count();
        printf("exception no throw cost %d vec %lu\n", ms_inte_no, global_vec.size());
    }
    {
        auto t1 = high_resolution_clock::now();
        loop_main(exception_all_throw_call);
        auto t2 = high_resolution_clock::now();
        ms_inte_all = duration_cast<milliseconds>(t2 - t1).count();
        printf("exception all throw cost %d vec %lu\n", ms_inte_all, global_vec.size());
    }
    {
        auto t1 = high_resolution_clock::now();
        loop_main(no_exception_call);
        auto t2 = high_resolution_clock::now();
        ms_intnoe = duration_cast<milliseconds>(t2 - t1).count();
        printf("no exception cost %d vec %lu\n", ms_intnoe, global_vec.size());
    }
    printf("loss exception no throw %f\n", (ms_inte_no - ms_intnoe) * 1.0 / ms_intnoe);
    printf("loss exception all throw %f\n", (ms_inte_all - ms_intnoe) * 1.0 / ms_intnoe);
}

And I compile and run it with -O2, and find the above post is true. The cost of normal path is quite low, however, the penalty of exception path is very high.

./exception_test
1619
exception no throw cost 118 vec 45
1619
exception all throw cost 21453 vec 45
1619
no exception cost 114 vec 45
loss exception no throw 0.035088
loss exception all throw 187.184211

Though my test is very extreme, we either all throw or no throw. However, I wonder if the exception is more often to be thrown, is there any way that we can somehow reduce the penalty of the exception path?

calvin
  • 2,125
  • 2
  • 21
  • 38
  • 3
    If a function is constantly throwing exceptions to the point it does impact performance, it's time to look at it and figure out why, and how to prevent them in the future. There is probably a design flaw that needs to be corrected. – Dave S Mar 07 '23 at 07:07
  • 2
    Exceptions should be exceptional, if your function is normally throwing, especially in performance critical sections, then you might want to use another mechanism to indicate whatever condition the exception is indicating – Alan Birtles Mar 07 '23 at 07:23
  • Unless I'm mistaken the only cost you can control is the allocation cost of the thrown object. Use something cheap without dynamic error messages and the like (e.g. compare `std::bad_alloc` with `std::runtime_error`). But that will have only minor effects. Use alternative means such as error codes or callbacks for higher performance – Homer512 Mar 07 '23 at 07:26
  • 1
    The code here tests the speed of totally inappropriate uses of exceptions. Every one of those `try ... catch` uses should be replaced by an appropriate `if` statement, and the called functions should return appropriate values, not throw exceptions. So, yes, exceptions are horribly slow when misused. So don't to that. – Pete Becker Mar 07 '23 at 14:25
  • @PeteBecker Sorry, I think I don't get your point. What I want to do is to mutate `int& a` and `global_vec` either by a normal path of a try-catch block or by a exception path of a try-catch block. That is to say `exception_no_throw_call` will do all the mutations without actually raise any exceptions, while `exception_all_throw_call` will do all the mutations when the exception is raised(which is always this case). In this way, I can compare the time to do the mutations in either the normal way or the exceptional way. – calvin Mar 07 '23 at 15:47
  • 1
    If your code throws exceptions often enough that the exception management code is a bottleneck the code is badly designed. Rethink it so it doesn't rely so heavily (and inappropriately) on exceptions. – Pete Becker Mar 07 '23 at 16:19

0 Answers0