@@ -67,6 +67,11 @@ def __init__(self):
67
67
# On exit, self.in_atexit = True
68
68
atexit .register (setattr , self , 'in_atexit' , True )
69
69
70
+ # cache a bound method on the instance, so that we don't have to
71
+ # re-create a bound method object all the time
72
+ self ._cached_bound_method_trace = self ._trace
73
+
74
+
70
75
def __repr__ (self ):
71
76
return "<PyTracer at 0x{:x}: {} lines in {} files>" .format (
72
77
id (self ),
@@ -105,7 +110,7 @@ def _trace(self, frame, event, arg_unused):
105
110
106
111
#self.log(":", frame.f_code.co_filename, frame.f_lineno, frame.f_code.co_name + "()", event)
107
112
108
- if (self .stopped and sys .gettrace () == self ._trace ): # pylint: disable=comparison-with-callable
113
+ if (self .stopped and sys .gettrace () == self ._cached_bound_method_trace ): # pylint: disable=comparison-with-callable
109
114
# The PyTrace.stop() method has been called, possibly by another
110
115
# thread, let's deactivate ourselves now.
111
116
if 0 :
@@ -129,12 +134,13 @@ def _trace(self, frame, event, arg_unused):
129
134
context_maybe = self .should_start_context (frame )
130
135
if context_maybe is not None :
131
136
self .context = context_maybe
132
- self . started_context = True
137
+ started_context = True
133
138
self .switch_context (self .context )
134
139
else :
135
- self . started_context = False
140
+ started_context = False
136
141
else :
137
- self .started_context = False
142
+ started_context = False
143
+ self .started_context = started_context
138
144
139
145
# Entering a new frame. Decide if we should trace in this file.
140
146
self ._activity = True
@@ -143,23 +149,33 @@ def _trace(self, frame, event, arg_unused):
143
149
self .cur_file_data ,
144
150
self .cur_file_name ,
145
151
self .last_line ,
146
- self . started_context ,
152
+ started_context ,
147
153
)
148
154
)
155
+
156
+ # Improve tracing performance: when calling a function, both caller
157
+ # and callee are often within the same file. if that's the case, we
158
+ # don't have to re-check whether to trace the corresponding
159
+ # function (which is a little bit espensive since it involves
160
+ # dictionary lookups). This optimization is only correct if we
161
+ # didn't start a context.
149
162
filename = frame .f_code .co_filename
150
- self .cur_file_name = filename
151
- disp = self .should_trace_cache .get (filename )
152
- if disp is None :
153
- disp = self .should_trace (filename , frame )
154
- self .should_trace_cache [filename ] = disp
155
-
156
- self .cur_file_data = None
157
- if disp .trace :
158
- tracename = disp .source_filename
159
- if tracename not in self .data :
160
- self .data [tracename ] = set ()
161
- self .cur_file_data = self .data [tracename ]
162
- else :
163
+ if filename != self .cur_file_name or started_context :
164
+ self .cur_file_name = filename
165
+ disp = self .should_trace_cache .get (filename )
166
+ if disp is None :
167
+ disp = self .should_trace (filename , frame )
168
+ self .should_trace_cache [filename ] = disp
169
+
170
+ self .cur_file_data = None
171
+ if disp .trace :
172
+ tracename = disp .source_filename
173
+ if tracename not in self .data :
174
+ self .data [tracename ] = set ()
175
+ self .cur_file_data = self .data [tracename ]
176
+ else :
177
+ frame .f_trace_lines = False
178
+ elif not self .cur_file_data :
163
179
frame .f_trace_lines = False
164
180
165
181
# The call event is really a "start frame" event, and happens for
@@ -225,7 +241,7 @@ def _trace(self, frame, event, arg_unused):
225
241
if self .started_context :
226
242
self .context = None
227
243
self .switch_context (None )
228
- return self ._trace
244
+ return self ._cached_bound_method_trace
229
245
230
246
def start (self ):
231
247
"""Start this Tracer.
@@ -243,10 +259,10 @@ def start(self):
243
259
# function, but we are marked as running again, so maybe it
244
260
# will be ok?
245
261
#self.log("~", "starting on different threads")
246
- return self ._trace
262
+ return self ._cached_bound_method_trace
247
263
248
- sys .settrace (self ._trace )
249
- return self ._trace
264
+ sys .settrace (self ._cached_bound_method_trace )
265
+ return self ._cached_bound_method_trace
250
266
251
267
def stop (self ):
252
268
"""Stop this Tracer."""
@@ -271,9 +287,10 @@ def stop(self):
271
287
# so don't warn if we are in atexit on PyPy and the trace function
272
288
# has changed to None.
273
289
dont_warn = (env .PYPY and env .PYPYVERSION >= (5 , 4 ) and self .in_atexit and tf is None )
274
- if (not dont_warn ) and tf != self ._trace : # pylint: disable=comparison-with-callable
290
+ if (not dont_warn ) and tf != self ._cached_bound_method_trace : # pylint: disable=comparison-with-callable
275
291
self .warn (
276
- f"Trace function changed, data is likely wrong: { tf !r} != { self ._trace !r} " ,
292
+ f"Trace function changed, data is likely wrong: "
293
+ f"{ tf !r} != { self ._cached_bound_method_trace !r} " ,
277
294
slug = "trace-changed" ,
278
295
)
279
296
0 commit comments