Slide 1

Slide 1 text

Debugging in Python 3.6: Better, Faster, Stronger Elizaveta Shashkova JetBrains
 
 PyCon US 2017

Slide 2

Slide 2 text

Bio • Software developer of PyCharm IDE at JetBrains • Debugger • Saint Petersburg, Russia 2

Slide 3

Slide 3 text

Debugging • Adding print statements • Logging 3

Slide 4

Slide 4 text

Debugging • Adding print statements • Logging • Special tools: debuggers 4

Slide 5

Slide 5 text

Debugger’s Performance 5 Run Debug Debuggers are 30 times slower

Slide 6

Slide 6 text

Contents • Tracing debugger • Python 3.6 • Frame evaluation debugger • Results 6

Slide 7

Slide 7 text

Contents • Tracing debugger • Python 3.6 • Frame evaluation debugger • Results 7

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

Build Python Debugger • Breakpoints • Stepping 15

Slide 16

Slide 16 text

Tracing Debugger • Suspend program if breakpoint’s line equals frame.f_lineno • Handle events for stepping 16

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

Performance 20 Run Tracing Breakpoints 0,80 sec 6,85 sec 19,81 sec

Slide 21

Slide 21 text

Performance 21 Run Tracing Breakpoints 0,80 sec 6,85 sec 19,81 sec

Slide 22

Slide 22 text

Performance 22 Run Tracing Breakpoints 0,80 sec 6,85 sec 19,81 sec

Slide 23

Slide 23 text

Performance 23 Run Tracing Breakpoints 0,80 sec 6,85 sec 19,81 sec ~ 25 times slower!

Slide 24

Slide 24 text

Problem • Tracing call on every line 24

Slide 25

Slide 25 text

Contents • Tracing debugger • Python 3.6 • Frame evaluation debugger • Results 25

Slide 26

Slide 26 text

Contents • Tracing debugger • Python 3.6 • Frame evaluation debugger • Results 26

Slide 27

Slide 27 text

Python 3.6 27

Slide 28

Slide 28 text

Python 3.6 • New frame evaluation API • PEP 523 28

Slide 29

Slide 29 text

PEP 523 • Handle evaluation of frames • Add a new field to code objects 29

Slide 30

Slide 30 text

Frame Evaluation def frame_eval(frame, exc): func_name = frame.f_code.co_name line_number = frame.f_lineno print(line_number, func_name) return _PyEval_EvalFrameDefault(frame, exc) 1 2 3 4 5 6 7 8 9 30

Slide 31

Slide 31 text

def frame_eval(frame, exc): func_name = frame.f_code.co_name a line_number = frame.f_lineno print(line_number, func_name) return _PyEval_EvalFrameDefault(frame, exc) 1 2 3 4 5 6 7 8 9 Frame Evaluation 31

Slide 32

Slide 32 text

def frame_eval(frame, exc): func_name = frame.f_code.co_name line_number = frame.f_lineno print(line_number, func_name) return _PyEval_EvalFrameDefault(frame, exc) 1 2 3 4 5 6 7 8 9 Frame Evaluation 32

Slide 33

Slide 33 text

def frame_eval(frame, exc): func_name = frame.f_code.co_name line_number = frame.f_lineno print(line_number, func_name) return _PyEval_EvalFrameDefault(frame, exc) 1 2 3 4 5 6 7 8 9 Frame Evaluation 33

Slide 34

Slide 34 text

def frame_eval(frame, exc): func_name = frame.f_code.co_name line_number = frame.f_lineno print(line_number, func_name) return _PyEval_EvalFrameDefault(frame, exc) 1 2 3 4 5 6 7 8 9 Frame Evaluation 34

Slide 35

Slide 35 text

def frame_eval(frame, exc): func_name = frame.f_code.co_name line_number = frame.f_lineno print(line_number, func_name) return _PyEval_EvalFrameDefault(frame, exc) def set_frame_eval(): state = PyThreadState_Get() state.interp.eval_frame = frame_eval 1 2 3 4 5 6 7 8 9 Frame Evaluation 35

Slide 36

Slide 36 text

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() 36

Slide 37

Slide 37 text

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 37

Slide 38

Slide 38 text

Custom Frame Evaluation • It works! • Executed while entering a frame • Access to frame and code object 38

Slide 39

Slide 39 text

Contents • Tracing debugger • Python 3.6 • Frame evaluation debugger • Results 39

Slide 40

Slide 40 text

Problem • Tracing call on every line 40

Slide 41

Slide 41 text

Problem • Tracing call on every line • Remove the tracing function! 41

Slide 42

Slide 42 text

Replace tracing function with frame evaluation function 42

Slide 43

Slide 43 text

Contents • Tracing debugger • Python 3.6 • Frame evaluation debugger • Results 43

Slide 44

Slide 44 text

Build Python Debugger • Breakpoints • Stepping 44

Slide 45

Slide 45 text

Breakpoints • Access to the whole code object 45

Slide 46

Slide 46 text

Breakpoints • Access to the whole code object • Insert breakpoint’s code into frame’s code 46

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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 51

Slide 52

Slide 52 text

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 52

Slide 53

Slide 53 text

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 53

Slide 54

Slide 54 text

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 54

Slide 55

Slide 55 text

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 55

Slide 56

Slide 56 text

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 56

Slide 57

Slide 57 text

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 57

Slide 58

Slide 58 text

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 58

Slide 59

Slide 59 text

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 59

Slide 60

Slide 60 text

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 60

Slide 61

Slide 61 text

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() 61

Slide 62

Slide 62 text

Bytecode Modification • Insert breakpoint’s code • Update arguments and offsets 62

Slide 63

Slide 63 text

Bytecode Modification • Insert breakpoint’s code • Update arguments and offsets • 200 lines in Python 63

Slide 64

Slide 64 text

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() ?! 64

Slide 65

Slide 65 text

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 65

Slide 66

Slide 66 text

Build Python Debugger • Breakpoints • Stepping 66

Slide 67

Slide 67 text

Stepping • Inserting temporary breakpoint on every line • Use old tracing function 67

Slide 68

Slide 68 text

Frame evaluation debugger is ready! 68

Slide 69

Slide 69 text

Example 1 def calculate(): sum = 0 for i in range(10 ** 7): sum += i return sum 1 2 3 4 5 69

Slide 70

Slide 70 text

Example 1 70 Run Tracing Frame 
 evaluation 0,80 sec 19,81 sec 0,81 sec

Slide 71

Slide 71 text

Example 1 71 Run Tracing Frame 
 evaluation 0,80 sec 19,81 sec 0,81 sec

Slide 72

Slide 72 text

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 72

Slide 73

Slide 73 text

Example 2 73 Run Tracing Frame 
 evaluation 1,73 sec 43,58 sec 37,41 sec

Slide 74

Slide 74 text

PEP 523 • Handle evaluation of frames • Add a new field to code objects 74

Slide 75

Slide 75 text

PEP 523 • Expand PyCodeObject struct • co_extra - “scratch space” for the code object • Mark frames without breakpoints 75

Slide 76

Slide 76 text

Mark Frames def frame_eval(frame, exc): flag = _PyCode_GetExtra(frame.f_code, index) if flag == NO_BREAKS_IN_FRAME: return _PyEval_EvalFrameDefault(frame, exc)
 
 # check for breakpoints ... 1 2 3 4 5 6 7 8 9 76

Slide 77

Slide 77 text

Example 2 77 Run Tracing Frame 
 evaluation 1,73 sec 43,58 sec 1,91 sec

Slide 78

Slide 78 text

PEP 523 • Handle evaluation of frames • Add a new field to code objects 78

Slide 79

Slide 79 text

Contents • Tracing debugger • Python 3.6 • Frame evaluation debugger • Results 79

Slide 80

Slide 80 text

Contents • Tracing debugger • Python 3.6 • Frame evaluation debugger • Results 80

Slide 81

Slide 81 text

Real Life Example 81

Slide 82

Slide 82 text

Real Life Example • Included into PyCharm 2017.1 • Works in production 82

Slide 83

Slide 83 text

PyCharm 83 Tracing
 w/o Cython Tracing
 with Cython Frame 
 evaluation 11,59 sec 5,66 sec 0,28 sec

Slide 84

Slide 84 text

Frame evaluation rocks! 84

Slide 85

Slide 85 text

Disadvantages • More complicated • Only with CPython • Only with Python 3.6 85

Slide 86

Slide 86 text

Frame Evaluation • Let’s move to Python 3.6! 86

Slide 87

Slide 87 text

Frame Evaluation • Let’s move to Python 3.6! • Let’s find another use cases! 87

Slide 88

Slide 88 text

Use cases def maximum(a, b): if a > b: return a # breakpoint else: return b 1 2 3 4 5 6 7 8 breakpoint() 88

Slide 89

Slide 89 text

PEP 523 • Pyjion project • JIT for Python 89

Slide 90

Slide 90 text

Frame Evaluation • Let’s move to Python 3.6! • Let’s find another use cases! 90

Slide 91

Slide 91 text

Links • Prototype: https://github.com/ Elizaveta239/frame-eval • PyCharm Community Edition source code 91

Slide 92

Slide 92 text

Questions? • Prototype: https://github.com/ Elizaveta239/frame-eval • PyCharm Community Edition source code 92 @lisa_shashkova