pythonperformanceexceptionmicro-optimization

Cost of exception handlers in Python


In another question, the accepted answer suggested replacing a (very cheap) if statement in Python code with a try/except block to improve performance.

Coding style issues aside, and assuming that the exception is never triggered, how much difference does it make (performance-wise) to have an exception handler, versus not having one, versus having a compare-to-zero if-statement?


Solution

  • Why don't you measure it using the timeit module? That way you can see whether it's relevant to your application.

    OK, so I've just tried the following (using Python 3.11.1 on Windows 11):

    import timeit
    
    statements=["""\
    try:
        b = 10/a
    except ZeroDivisionError:
        pass""",
    """\
    if a:
        b = 10/a""",
    "b = 10/a"]
    
    for a in (1,0):
        for s in statements:
            t = timeit.Timer(stmt=s, setup='a={}'.format(a))
            print("a = {}\n{}".format(a,s))
            print("%.2f usec/pass\n" % (1000000 * t.timeit(number=100000)/100000))
    

    Result:

    a = 1
    try:
        b = 10/a
    except ZeroDivisionError:
        pass
    0.06 usec/pass
    
    a = 1
    if a:
        b = 10/a
    0.05 usec/pass
    
    a = 1
    b = 10/a
    0.03 usec/pass
    
    a = 0
    try:
        b = 10/a
    except ZeroDivisionError:
        pass
    0.27 usec/pass
    
    a = 0
    if a:
        b = 10/a
    0.02 usec/pass
    
    a = 0
    b = 10/a
    Traceback (most recent call last):
      File "<stdin>", line 5, in <module>
      File "C:\Python311\Lib\timeit.py", line 178, in timeit
        timing = self.inner(it, self.timer)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "<timeit-src>", line 6, in inner
    ZeroDivisionError: division by zero
    

    As you can see, there is not much of a difference between using a try/except clause vs. an explicit if statement, unless the exception gets triggered. (And of course, not having any control structure is fastest, though not by much, and it will crash the program if anything goes wrong).

    Compare this to the results obtained in 2010:

    a = 1
    try:
        b = 10/a
    except ZeroDivisionError:
        pass
    0.25 usec/pass
    
    a = 1
    if a:
        b = 10/a
    0.29 usec/pass
    
    a = 1
    b = 10/a
    0.22 usec/pass
    
    a = 0
    try:
        b = 10/a
    except ZeroDivisionError:
        pass
    0.57 usec/pass
    
    a = 0
    if a:
        b = 10/a
    0.04 usec/pass
    
    a = 0
    b = 10/a
    ZeroDivisionError: int division or modulo by zero
    

    I appears that the PC I'm using now is about twice as fast as the one I had back then. The cost of handling an Exception appears identical, and the "normal" operations (arithmetic) have been improved even more than the handling of control structures, but the point from all those years ago still stands:

    It's all within the same order of magnitude and unlikely to matter either way. Only if the condition is actually met (often), then the if version is significantly faster.