2016-04-15 77 views
7

我知道python中的異常在try中很快,但它涉及到catch時可能很昂貴。在python中捕獲性能的例外

這是否意味着:

try: 
    some code 
except MyException: 
    pass 

比這快?

try: 
    some code 
except MyException as e: 
    pass 
+1

是否「一些代碼」拋出錯誤的問題或不? –

+0

@HannesOvrén它呢 – maazza

回答

8

除了弗朗西斯的回答,似乎抓的(相對)昂貴的部分之一是例外匹配:

>>> timeit.timeit('try:\n raise KeyError\nexcept KeyError:\n pass', number=1000000) 
1.1587663322268327 
>>> timeit.timeit('try:\n raise KeyError\nexcept:\n pass', number=1000000) 
0.9180641582179874 

綜觀(CPython的2)拆卸:

>>> def f(): 
...  try: 
...   raise KeyError 
...  except KeyError: 
...   pass 
... 
>>> def g(): 
...  try: 
...   raise KeyError 
...  except: 
...   pass 
... 
>>> dis.dis(f) 
    2   0 SETUP_EXCEPT   10 (to 13) 

    3   3 LOAD_GLOBAL    0 (KeyError) 
       6 RAISE_VARARGS   1 
       9 POP_BLOCK   
      10 JUMP_FORWARD   17 (to 30) 

    4  >> 13 DUP_TOP    
      14 LOAD_GLOBAL    0 (KeyError) 
      17 COMPARE_OP    10 (exception match) 
      20 POP_JUMP_IF_FALSE  29 
      23 POP_TOP    
      24 POP_TOP    
      25 POP_TOP    

    5   26 JUMP_FORWARD    1 (to 30) 
     >> 29 END_FINALLY   
     >> 30 LOAD_CONST    0 (None) 
      33 RETURN_VALUE   
>>> dis.dis(g) 
    2   0 SETUP_EXCEPT   10 (to 13) 

    3   3 LOAD_GLOBAL    0 (KeyError) 
       6 RAISE_VARARGS   1 
       9 POP_BLOCK   
      10 JUMP_FORWARD    7 (to 20) 

    4  >> 13 POP_TOP    
      14 POP_TOP    
      15 POP_TOP    

    5   16 JUMP_FORWARD    1 (to 20) 
      19 END_FINALLY   
     >> 20 LOAD_CONST    0 (None) 
      23 RETURN_VALUE   

請注意,catch塊無論如何都會加載異常並將其與KeyError相匹配。的確,看着except KeyError as ke情況:

>>> def f2(): 
...  try: 
...   raise KeyError 
...  except KeyError as ke: 
...   pass 
... 
>>> dis.dis(f2) 
    2   0 SETUP_EXCEPT   10 (to 13) 

    3   3 LOAD_GLOBAL    0 (KeyError) 
       6 RAISE_VARARGS   1 
       9 POP_BLOCK   
      10 JUMP_FORWARD   19 (to 32) 

    4  >> 13 DUP_TOP    
      14 LOAD_GLOBAL    0 (KeyError) 
      17 COMPARE_OP    10 (exception match) 
      20 POP_JUMP_IF_FALSE  31 
      23 POP_TOP    
      24 STORE_FAST    0 (ke) 
      27 POP_TOP    

    5   28 JUMP_FORWARD    1 (to 32) 
     >> 31 END_FINALLY   
     >> 32 LOAD_CONST    0 (None) 
      35 RETURN_VALUE  

唯一的區別是一個STORE_FAST存儲異常值(在匹配的情況下)。同樣,有幾個例外相匹配:

>>> def f(): 
...  try: 
...   raise ValueError 
...  except KeyError: 
...   pass 
...  except IOError: 
...   pass 
...  except SomeOtherError: 
...   pass 
...  except: 
...   pass 
... 
>>> dis.dis(f) 
    2   0 SETUP_EXCEPT   10 (to 13) 

    3   3 LOAD_GLOBAL    0 (ValueError) 
       6 RAISE_VARARGS   1 
       9 POP_BLOCK   
      10 JUMP_FORWARD   55 (to 68) 

    4  >> 13 DUP_TOP    
      14 LOAD_GLOBAL    1 (KeyError) 
      17 COMPARE_OP    10 (exception match) 
      20 POP_JUMP_IF_FALSE  29 
      23 POP_TOP    
      24 POP_TOP    
      25 POP_TOP    

    5   26 JUMP_FORWARD   39 (to 68) 

    6  >> 29 DUP_TOP    
      30 LOAD_GLOBAL    2 (IOError) 
      33 COMPARE_OP    10 (exception match) 
      36 POP_JUMP_IF_FALSE  45 
      39 POP_TOP    
      40 POP_TOP    
      41 POP_TOP    

    7   42 JUMP_FORWARD   23 (to 68) 

    8  >> 45 DUP_TOP    
      46 LOAD_GLOBAL    3 (SomeOtherError) 
      49 COMPARE_OP    10 (exception match) 
      52 POP_JUMP_IF_FALSE  61 
      55 POP_TOP    
      56 POP_TOP    
      57 POP_TOP    

    9   58 JUMP_FORWARD    7 (to 68) 

10  >> 61 POP_TOP    
      62 POP_TOP    
      63 POP_TOP    

11   64 JUMP_FORWARD    1 (to 68) 
      67 END_FINALLY   
     >> 68 LOAD_CONST    0 (None) 
      71 RETURN_VALUE  

將重複異常,並試圖通過一個與之相匹配的對列出的每個例外,直到把它創立一個比賽,這是(可能)是什麼在被暗示爲「差抓住表現「。

6

我認爲兩者是相同的在速度方面:

>>> timeit.timeit('try:\n raise KeyError\nexcept KeyError:\n pass', number=1000000) 
0.7168641227143269 
>>> timeit.timeit('try:\n raise KeyError\nexcept KeyError as e:\n pass', number=1000000) 
0.7733279216613766 
3

Python程序是由代碼塊構建的。塊是作爲一個單元執行的一段Python程序文本。在Python核心地塊被表示爲結構basicblock:

CPython的/ Python的/ compile.c

typedef struct basicblock_ { 
    /* Each basicblock in a compilation unit is linked via b_list in the 
     reverse order that the block are allocated. b_list points to the next 
     block, not to be confused with b_next, which is next by control flow. */ 
    struct basicblock_ *b_list; 
    /* number of instructions used */ 
    int b_iused; 
    /* length of instruction array (b_instr) */ 
    int b_ialloc; 
    /* pointer to an array of instructions, initially NULL */ 
    struct instr *b_instr; 
    /* If b_next is non-NULL, it is a pointer to the next 
     block reached by normal control flow. */ 
    struct basicblock_ *b_next; 
    /* b_seen is used to perform a DFS of basicblocks. */ 
    unsigned b_seen : 1; 
    /* b_return is true if a RETURN_VALUE opcode is inserted. */ 
    unsigned b_return : 1; 
    /* depth of stack upon entry of block, computed by stackdepth() */ 
    int b_startdepth; 
    /* instruction offset for block, computed by assemble_jump_offsets() */ 
    int b_offset; 
} basicblock; 

循環,try/except和try/finally語句來處理不同的東西。對於此3個語句用於幀塊:

CPython的/ Python的/ compile.c

enum fblocktype { LOOP, EXCEPT, FINALLY_TRY, FINALLY_END }; 

struct fblockinfo { 
    enum fblocktype fb_type; 
    basicblock *fb_block; 
}; 

A碼塊是在執行幀執行。

CPython的/包含/ frameobject.h

typedef struct _frame { 
    PyObject_VAR_HEAD 
    struct _frame *f_back;  /* previous frame, or NULL */ 
    PyCodeObject *f_code;  /* code segment */ 
    PyObject *f_builtins;  /* builtin symbol table (PyDictObject) */ 
    PyObject *f_globals;  /* global symbol table (PyDictObject) */ 
    PyObject *f_locals;   /* local symbol table (any mapping) */ 
    PyObject **f_valuestack; /* points after the last local */ 
    /* Next free slot in f_valuestack. Frame creation sets to f_valuestack. 
     Frame evaluation usually NULLs it, but a frame that yields sets it 
     to the current stack top. */ 
    PyObject **f_stacktop; 
    PyObject *f_trace;   /* Trace function */ 

    /* In a generator, we need to be able to swap between the exception 
     state inside the generator and the exception state of the calling 
     frame (which shouldn't be impacted when the generator "yields" 
     from an except handler). 
     These three fields exist exactly for that, and are unused for 
     non-generator frames. See the save_exc_state and swap_exc_state 
     functions in ceval.c for details of their use. */ 
    PyObject *f_exc_type, *f_exc_value, *f_exc_traceback; 
    /* Borrowed reference to a generator, or NULL */ 
    PyObject *f_gen; 

    int f_lasti;    /* Last instruction if called */ 
    /* Call PyFrame_GetLineNumber() instead of reading this field 
     directly. As of 2.3 f_lineno is only valid when tracing is 
     active (i.e. when f_trace is set). At other times we use 
     PyCode_Addr2Line to calculate the line from the current 
     bytecode index. */ 
    int f_lineno;    /* Current line number */ 
    int f_iblock;    /* index in f_blockstack */ 
    char f_executing;   /* whether the frame is still executing */ 
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */ 
    PyObject *f_localsplus[1]; /* locals+stack, dynamically sized */ 
} PyFrameObject; 

幀包含一些管理信息(用於調試),並確定在何處以及碼塊執行完成之後執行如何繼續。當你使用'as'語句(在'import something as'或'except exception as'語句中)時,你只需要進行名稱綁定操作。即Python只需在frame對象的* f_locals符號表中添加對象的引用即可。因此在運行時不會有開銷。

但是在解析時你會有一些開銷。

CPython的/模塊/ parsermodule.c

static int 
validate_except_clause(node *tree) 
{ 
    int nch = NCH(tree); 
    int res = (validate_ntype(tree, except_clause) 
       && ((nch == 1) || (nch == 2) || (nch == 4)) 
       && validate_name(CHILD(tree, 0), "except")); 

    if (res && (nch > 1)) 
     res = validate_test(CHILD(tree, 1)); 
    if (res && (nch == 4)) 
     res = (validate_name(CHILD(tree, 2), "as") 
       && validate_ntype(CHILD(tree, 3), NAME)); 

    return (res); 
} 

但是,在我看來,這是可以忽略不計

3

美中不足的是不貴,出現相對較慢的部分是堆棧跟蹤本身的創建,以及如果需要的話,隨後的堆棧展開。

我知道的所有基於堆棧的語言都允許您捕獲堆棧跟蹤信息,因此需要執行這些操作。

  1. raise被調用時收集堆棧信息。請注意,Java 1.7允許您抑制堆棧收集,速度更快,但是會丟失大量有用的信息。沒有明智的方法讓語言知道誰會抓住它,所以忽略異常並不會有幫助,因爲無論如何它必須執行大部分工作。
  2. 如果我們正在引發一個異常,那麼展開堆棧即釋放所有內存並放回,直到我們碰到有效的catch。

與上述兩項操作相比,捕獲量是微乎其微的。這裏有一些代碼來演示隨着堆棧深度的增加,性能下降。

#!/usr/bin/env python 
import os 
import re 
import time 
import pytest 

max_depth = 10 
time_start = [0] * (max_depth + 1) 
time_stop = [0] * (max_depth + 1) 
time_total = [0] * (max_depth + 1) 
depth = [] 
for x in range(0, max_depth): 
    depth.append(x) 

@pytest.mark.parametrize('i', depth) 
def test_stack(benchmark, i): 
    benchmark.pedantic(catcher2, args=(i,i), rounds=10, iterations=1000) 

#@pytest.mark.parametrize('d', depth) 
#def test_recursion(benchmark, d): 
# benchmark.pedantic(catcher, args=(d,), rounds=50, iterations=50) 

def catcher(i, depth): 
    try: 
    ping(i, depth) 
    except Exception: 
    time_total[depth] += time.clock() - time_start[depth] 

def recurse(i, depth): 
    if(d > 0): 
    recurse(--i, depth) 
    thrower(depth) 

def catcher2(i, depth): 
    global time_total 
    global time_start 
    try: 
    ping(i, depth) 
    except Exception: 
    time_total[depth] += time.clock() - time_start[depth] 

def thrower(depth): 
    global time_start 
    time_start[depth] = time.clock() 
    raise Exception('wtf') 

def ping(i, depth): 
    if(i < 1): thrower(i, depth) 
    return pong(i, depth) 

def pong(i, depth): 
    if(i < 0): thrower(i,depth) 
    return ping(i - 4, depth) 

if __name__ == "__main__": 
    rounds  = 200000 
    class_time = 0 
    class_start = time.clock() 
    for round in range(0, rounds): 
    ex = Exception() 
    class_time = time.clock() - class_start 
    print("%d ex = Exception()'s %f" % (rounds, class_time)) 

    for depth in range(0, max_depth): 
    #print("Depth %d" % depth) 
    for round in range(0, rounds): 
     catcher(depth, depth) 

    for rep in range(0, max_depth): 
    print("depth=%d time=%f" % (rep, time_total[rep]/1000000)) 

的輸出,時間(時間是相對的)需要在Python中調用Exception()

200000 ex = Exception()'s 0.040469 

depth=0 time=0.103843 
depth=1 time=0.246050 
depth=2 time=0.401459 
depth=3 time=0.565742 
depth=4 time=0.736362 
depth=5 time=0.921993 
depth=6 time=1.102257 
depth=7 time=1.278089 
depth=8 time=1.463500 
depth=9 time=1.657082 

有人比我更可能是能夠獲得py.test在最後打印的時間安排。

請注意,幾周前,有人問到有關Java的一個非常類似的問題。這是一個非常豐富的螺紋無論使用何種語言的...

Which part of throwing an Exception is expensive?