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

Transcript

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

More Decks by Elizaveta Shashkova

Other Decks in Technology