2016-04-15 10 views
7

So che le eccezioni in Python sono veloci quando si tratta di try ma che può essere costoso quando si tratta del problema.Eccezioni che catturano prestazioni in python

Questo significa che:

try: 
    some code 
except MyException: 
    pass 

è più veloce di questo?

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

significa "codice" buttare l'errore in questione o no? –

+0

@ HannesOvrén lo fa – maazza

risposta

8

Oltre alla risposta di Francesco, sembra che uno dei (relativamente) parte costosa del fermo è l'abbinamento eccezione :

>>> 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 

Guardando la (CPython 2) lo smontaggio:

>>> 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   

Si noti che il blocco catch carica comunque l'eccezione e la confronta con uno KeyError. In effetti, guardando il caso 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  

L'unica differenza è una sola STORE_FAST per memorizzare il valore eccezione (in caso di una partita). Allo stesso modo, avendo diversi eccezione partite:

>>> 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  

duplicherà l'eccezione e cercare di abbinare contro ogni esclusione elencati, uno per uno fino a che non fonda una partita, che è (probabilmente) ciò che viene suggerita come 'poveri prendere spettacolo '.

6

penso che i due sono la stessa in termini di velocità:

>>> 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

Un programma Python è costituito da blocchi di codice. Un blocco è un pezzo di testo del programma Python che viene eseguito come un'unità. In Python blocco nucleo è rappresentato come struct 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; 

Loops, try/except e provare/finally manipolati qualcosa di diverso. Per questo 3 istruzioni vengono utilizzati blocchi telaio:

CPython/Python/compile.c

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

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

Un blocco di codice viene eseguito in una cornice di esecuzione.

CPython/Include/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; 

Una cornice contiene delle informazioni amministrative (utilizzate per il debug) e determina dove e come l'esecuzione continua dopo l'esecuzione del blocco di codice ha completato. Quando si utilizza l'istruzione 'as' (nelle istruzioni 'import something as' o 'except Exception as') è sufficiente denominare l'operazione di bind. Cioè Python aggiunge semplicemente un riferimento all'oggetto nella tabella dei simboli * f_locals dell'oggetto frame.Quindi nessun sovraccarico in fase di esecuzione non sarà.

Ma si avrà un sovraccarico in fase di analisi.

CPython/Moduli/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); 
} 

Ma, a mio parere, questo può essere trascurato

3

La cattura non è costoso, le parti che appaiono relativamente lento sono la creazione dello stack trace stesso e, se necessario, il successivo svolgimento dello stack.

Tutte le lingue basate sullo stack di cui sono a conoscenza che consentono di acquisire tracce di stack devono eseguire queste operazioni.

  1. Quando si chiama raise raccogliere le informazioni sullo stack. Nota, Java 1.7 ti consente di sopprimere la raccolta dello stack ed è molto più veloce ma perdi molte informazioni utili. Non esiste un modo sensato per il linguaggio di sapere chi lo catturerà, quindi ignorare un'eccezione non aiuta perché deve comunque svolgere la maggior parte del lavoro.
  2. Se solleviamo un'eccezione, quindi srotola lo stack, quindi rilascia la memoria e torna indietro finché non raggiungiamo un valore valido.

Il fermo è minimo rispetto alle due operazioni precedenti. Ecco un codice per dimostrare che quando la profondità dello stack aumenta, le prestazioni diminuiscono.

#!/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)) 

L'uscita è, il tempo (i tempi sono relative) prendere per chiamare 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 

Qualcuno meglio a Python di me potrebbe essere in grado di ottenere py.test per stampare i tempi alla fine.

Nota: c'era una domanda molto simile a questa domanda su Java qualche settimana fa. E 'una discussione molto informativo indipendentemente dal linguaggio utilizzato ...

Which part of throwing an Exception is expensive?