Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Отладка в Python 3.6: Быстрее, Выше, Сильнее

Отладка в Python 3.6: Быстрее, Выше, Сильнее

В данном докладе мы узнаем, как работает новый интерфейс для вычисления фреймов, как он может помочь при создании быстрого отладчика, и почему такой быстрый отладчик невозможно было создать в предыдущих версиях языка Python. Для тех же, кто ещё не принял окончательное решение о переходе на Python 3.6, этот доклад даст несколько дополнительных причин, почему это стоит сделать.

Elizaveta Shashkova

July 17, 2017
Tweet

More Decks by Elizaveta Shashkova

Other Decks in Technology

Transcript

  1. 8 def tracefunc(frame, event, arg): print(frame.f_lineno, event) return tracefunc sys.settrace(tracefunc)

    1 2 3 4 5 6 sys.settrace(tracefunc) - set the system tracing function Tracing Function
  2. def foo(): friends = ["Bob", "Tom"] for f in friends:

    print("Hi %s!” % f) return len(friends) sys.settrace(tracefunc) foo() 1 2 3 4 5 6 7 8 9 Tracing Function 9
  3. 1 2 3 4 5 6 7 8 9 1

    call def foo(): friends = ["Bob", "Tom"] for f in friends: print("Hi %s!” % f) return len(friends) sys.settrace(tracefunc) foo() Tracing Function 10
  4. 1 2 3 4 5 6 7 8 9 1

    call 2 line def foo(): friends = ["Bob", "Tom"] for f in friends: print("Hi %s!” % f) return len(friends) sys.settrace(tracefunc) foo() Tracing Function 11
  5. 1 2 3 4 5 6 7 8 9 1

    call 2 line 3 line 4 line Hi Bob! def foo(): friends = ["Bob", "Tom"] for f in friends: print("Hi %s!” % f) return len(friends) sys.settrace(tracefunc) foo() Tracing Function 12
  6. 1 2 3 4 5 6 7 8 9 1

    call 2 line 3 line 4 line Hi Bob! 3 line 4 line Hi Tom! def foo(): friends = ["Bob", "Tom"] for f in friends: print("Hi %s!” % f) return len(friends) sys.settrace(tracefunc) foo() Tracing Function 13
  7. 1 2 3 4 5 6 7 8 9 1

    call 2 line 3 line 4 line Hi Bob! 3 line 4 line Hi Tom! 5 line 5 return def foo(): friends = ["Bob", "Tom"] for f in friends: print("Hi %s!” % f) return len(friends) sys.settrace(tracefunc) foo() Tracing Function 14
  8. Performance def foo(): friends = ["Bob", "Tom"] for f in

    friends: print("Hi %s!” % f) return len(friends) sys.settrace(tracefunc) foo() 1 2 3 4 5 6 7 8 9 1 call 2 line 3 line 4 line Hi Bob! 3 line 4 line Hi Tom! 5 line 5 return 17
  9. Example 1 def calculate(): sum = 0 for i in

    range(10 ** 7): sum += i return sum 1 2 3 4 5 6 7 8 9 18
  10. Example 1 def calculate(): sum = 0 for i in

    range(10 ** 7): sum += i return sum def tracefunc(frame, event, arg): return tracefunc 1 2 3 4 5 6 7 8 9 19
  11. Optimization 2 • Rewrite tracing function in C • Works

    only for CPython • ~ 4 times faster 28
  12. PEP 523 • Handle evaluation of frames • Add a

    new field to code objects • C API 34
  13. Frame Evaluation cdef frame_eval(PyFrameObject *frame, int exc): func_name = <object>

    frame.f_code.co_name line_number = <object> frame.f_lineno print(line_number, func_name) return _PyEval_EvalFrameDefault(frame, exc) 1 2 3 4 5 6 7 8 9 35
  14. cdef frame_eval(PyFrameObject *frame, int exc): func_name = <object> frame.f_code.co_name f

    line_number = <object> frame.f_lineno print(line_number, func_name) return _PyEval_EvalFrameDefault(frame, exc) 1 2 3 4 5 6 7 8 9 Frame Evaluation 36
  15. cdef frame_eval(PyFrameObject *frame, int exc): func_name = <object> frame.f_code.co_name line_number

    = <object> frame.f_lineno print(line_number, func_name) return _PyEval_EvalFrameDefault(frame, exc) 1 2 3 4 5 6 7 8 9 Frame Evaluation 37
  16. cdef frame_eval(PyFrameObject *frame, int exc): func_name = <object> frame.f_code.co_name line_number

    = <object> frame.f_lineno print(line_number, func_name) return _PyEval_EvalFrameDefault(frame, exc) 1 2 3 4 5 6 7 8 9 Frame Evaluation 38
  17. cdef frame_eval(PyFrameObject *frame, int exc): func_name = <object> frame.f_code.co_name line_number

    = <object> frame.f_lineno print(line_number, func_name) return _PyEval_EvalFrameDefault(frame, exc) 1 2 3 4 5 6 7 8 9 Frame Evaluation 39
  18. cdef frame_eval(PyFrameObject *frame, int exc): func_name = <object> frame.f_code.co_name line_number

    = <object> frame.f_lineno print(line_number, func_name) return _PyEval_EvalFrameDefault(frame, exc) def set_frame_eval(): cdef PyThreadState *state = \ PyThreadState_Get() state.interp.eval_frame = frame_eval 1 2 3 4 5 6 7 8 9 Frame Evaluation 40
  19. Example 1 2 3 4 5 6 7 8 9

    10 11 def first(): second() def second(): third() def third(): pass set_frame_eval() first() 41
  20. Example def first(): second() def second(): third() def third(): pass

    set_frame_eval() first() 1 2 3 4 5 6 7 8 9 10 11 1 first 4 second 7 third 42
  21. Custom Frame Evaluation • It works • Executed while entering

    a frame • Access to frame and code object 43
  22. Breakpoints • Access to the whole code object • Insert

    breakpoint’s code into frame’s code 51
  23. Breakpoints def maximum(a, b): if a > b: return a

    else: return b 1 2 3 4 5 6 7 8 52
  24. Breakpoints def maximum(a, b): if a > b: return a

    # breakpoint else: return b 1 2 3 4 5 6 7 8 53
  25. Breakpoints def maximum(a, b): if a > b: return a

    # breakpoint else: return b 1 2 3 4 5 6 7 8 breakpoint() 54
  26. Breakpoints def maximum(a, b): if a > b: breakpoint() return

    a # breakpoint else: return b 1 2 3 4 5 6 7 8 55
  27. Python Bytecode def maximum(a, b): if a > b: return

    a else: return b import dis dis.dis(maximum) 1 2 3 4 5 6 7 8 56
  28. Python Bytecode def maximum(a, b): if a > b: return

    a else: return b import dis dis.dis(maximum) 1 2 3 4 5 6 7 8 2 0 LOAD_FAST 0 (a) 2 LOAD_FAST 1 (b) 4 COMPARE_OP 4 (>) 6 POP_JUMP_IF_FALSE 12 3 8 LOAD_FAST 0 (a) 10 RETURN_VALUE 5 >> 12 LOAD_FAST 1 (b) 14 RETURN_VALUE 16 LOAD_CONST 0 (None) 18 RETURN_VALUE 57
  29. Python Bytecode def maximum(a, b): if a > b: return

    a else: return b import dis dis.dis(maximum) 1 2 3 4 5 6 7 8 2 0 LOAD_FAST 0 (a) 2 LOAD_FAST 1 (b) 4 COMPARE_OP 4 (>) 6 POP_JUMP_IF_FALSE 12 3 8 LOAD_FAST 0 (a) 10 RETURN_VALUE 5 >> 12 LOAD_FAST 1 (b) 14 RETURN_VALUE 16 LOAD_CONST 0 (None) 18 RETURN_VALUE 58
  30. Python Bytecode def maximum(a, b): if a > b: return

    a else: return b import dis dis.dis(maximum) 1 2 3 4 5 6 7 8 2 0 LOAD_FAST 0 (a) 2 LOAD_FAST 1 (b) 4 COMPARE_OP 4 (>) 6 POP_JUMP_IF_FALSE 12 3 8 LOAD_FAST 0 (a) 10 RETURN_VALUE 5 >> 12 LOAD_FAST 1 (b) 14 RETURN_VALUE 16 LOAD_CONST 0 (None) 18 RETURN_VALUE 59
  31. Python Bytecode def maximum(a, b): if a > b: return

    a else: return b import dis dis.dis(maximum) 1 2 3 4 5 6 7 8 2 0 LOAD_FAST 0 (a) 2 LOAD_FAST 1 (b) 4 COMPARE_OP 4 (>) 6 POP_JUMP_IF_FALSE 12 3 8 LOAD_FAST 0 (a) 10 RETURN_VALUE 5 >> 12 LOAD_FAST 1 (b) 14 RETURN_VALUE 16 LOAD_CONST 0 (None) 18 RETURN_VALUE 60
  32. Python Bytecode def maximum(a, b): if a > b: return

    a else: return b import dis dis.dis(maximum) 1 2 3 4 5 6 7 8 2 0 LOAD_FAST 0 (a) 2 LOAD_FAST 1 (b) 4 COMPARE_OP 4 (>) 6 POP_JUMP_IF_FALSE 12 3 8 LOAD_FAST 0 (a) 10 RETURN_VALUE 5 >> 12 LOAD_FAST 1 (b) 14 RETURN_VALUE 16 LOAD_CONST 0 (None) 18 RETURN_VALUE 61
  33. Python Bytecode 2 0 LOAD_FAST 0 (a) 2 LOAD_FAST 1

    (b) 4 COMPARE_OP 4 (>) 6 POP_JUMP_IF_FALSE 12 3 8 LOAD_FAST 0 (a) 10 RETURN_VALUE 5 >> 12 LOAD_FAST 1 (b) 14 RETURN_VALUE 16 LOAD_CONST 0 (None) 18 RETURN_VALUE 62
  34. 2 0 LOAD_FAST 0 (a) 2 LOAD_FAST 1 (b) 4

    COMPARE_OP 4 (>) 6 POP_JUMP_IF_FALSE 12 3 8 LOAD_FAST 0 (a) 10 RETURN_VALUE 5 >> 12 LOAD_FAST 1 (b) 14 RETURN_VALUE 16 LOAD_CONST 0 (None) 18 RETURN_VALUE Python Bytecode 63
  35. 2 0 LOAD_FAST 0 (a) 2 LOAD_FAST 1 (b) 4

    COMPARE_OP 4 (>) 6 POP_JUMP_IF_FALSE 12 3 8 LOAD_FAST 0 (a) 10 RETURN_VALUE 5 >> 12 LOAD_FAST 1 (b) 14 RETURN_VALUE 16 LOAD_CONST 0 (None) 18 RETURN_VALUE Python Bytecode 64
  36. 2 0 LOAD_FAST 0 (a) 2 LOAD_FAST 1 (b) 4

    COMPARE_OP 4 (>) 6 POP_JUMP_IF_FALSE 12 3 8 LOAD_FAST 0 (a) 10 RETURN_VALUE 5 >> 12 LOAD_FAST 1 (b) 14 RETURN_VALUE 16 LOAD_CONST 0 (None) 18 RETURN_VALUE Python Bytecode 65
  37. Bytecode Modification 2 0 LOAD_FAST 0 (a) 2 LOAD_FAST 1

    (b) 4 COMPARE_OP 4 (>) 6 POP_JUMP_IF_FALSE 12 3 8 LOAD_FAST 0 (a) 10 RETURN_VALUE 5 >> 12 LOAD_FAST 1 (b) 14 RETURN_VALUE 16 LOAD_CONST 0 (None) 18 RETURN_VALUE breakpoint() 66
  38. Bytecode Modification 2 0 LOAD_FAST 0 (a) 2 LOAD_FAST 1

    (b) 4 COMPARE_OP 4 (>) 6 POP_JUMP_IF_FALSE 12 3 8 LOAD_FAST 0 (a) 10 RETURN_VALUE 5 >> 12 LOAD_FAST 1 (b) 14 RETURN_VALUE 16 LOAD_CONST 0 (None) 18 RETURN_VALUE breakpoint() ?! 69
  39. Breakpoint Bytecode def _stop_at_break(): # a lot of code here

    def breakpoint(): _stop_at_break() 1 2 3 4 5 6 7 8 0 LOAD_GLOBAL 0 2 CALL_FUNCTION 0 4 POP_TOP 6 LOAD_CONST 0 8 RETURN_VALUE 70
  40. Example 1 def calculate(): sum = 0 for i in

    range(10 ** 7): sum += i return sum 1 2 3 4 5 74
  41. Example 2 def foo(): pass def calculate(): sum = 0

    for i in range(10 ** 7): foo() sum += i return sum 1 2 3 4 5 6 7 8 9 77
  42. PEP 523 • Expand PyCodeObject struct • co_extra - “scratch

    space” for the code object • Mark frames without breakpoints 80
  43. Mark Frames Cython 1 2 3 4 5 6 7

    8 9 81 cdef frame_eval(PyFrameObject *frame, int exc): flag = _PyCode_GetExtra(frame.f_code, index) if flag == NO_BREAKS_IN_FRAME: return _PyEval_EvalFrameDefault(frame, exc) # check for breakpoints ...
  44. Use cases def maximum(a, b): if a > b: return

    a # breakpoint else: return b 1 2 3 4 5 6 7 8 breakpoint() 92