diff -r e0f98cc25bf5 -r 32dd11232e06 src/eric7/DebugClients/Python/coverage/pytracer.py --- a/src/eric7/DebugClients/Python/coverage/pytracer.py Wed Jul 20 16:03:55 2022 +0200 +++ b/src/eric7/DebugClients/Python/coverage/pytracer.py Wed Jul 20 16:13:29 2022 +0200 @@ -67,6 +67,10 @@ # On exit, self.in_atexit = True atexit.register(setattr, self, 'in_atexit', True) + # Cache a bound method on the instance, so that we don't have to + # re-create a bound method object all the time. + self._cached_bound_method_trace = self._trace + def __repr__(self): return "<PyTracer at 0x{:x}: {} lines in {} files>".format( id(self), @@ -105,7 +109,7 @@ #self.log(":", frame.f_code.co_filename, frame.f_lineno, frame.f_code.co_name + "()", event) - if (self.stopped and sys.gettrace() == self._trace): # pylint: disable=comparison-with-callable + if (self.stopped and sys.gettrace() == self._cached_bound_method_trace): # pylint: disable=comparison-with-callable # The PyTrace.stop() method has been called, possibly by another # thread, let's deactivate ourselves now. if 0: @@ -129,12 +133,13 @@ context_maybe = self.should_start_context(frame) if context_maybe is not None: self.context = context_maybe - self.started_context = True + started_context = True self.switch_context(self.context) else: - self.started_context = False + started_context = False else: - self.started_context = False + started_context = False + self.started_context = started_context # Entering a new frame. Decide if we should trace in this file. self._activity = True @@ -143,22 +148,35 @@ self.cur_file_data, self.cur_file_name, self.last_line, - self.started_context, + started_context, ) ) - filename = frame.f_code.co_filename - self.cur_file_name = filename - disp = self.should_trace_cache.get(filename) - if disp is None: - disp = self.should_trace(filename, frame) - self.should_trace_cache[filename] = disp - self.cur_file_data = None - if disp.trace: - tracename = disp.source_filename - if tracename not in self.data: - self.data[tracename] = set() - self.cur_file_data = self.data[tracename] + # Improve tracing performance: when calling a function, both caller + # and callee are often within the same file. if that's the case, we + # don't have to re-check whether to trace the corresponding + # function (which is a little bit espensive since it involves + # dictionary lookups). This optimization is only correct if we + # didn't start a context. + filename = frame.f_code.co_filename + if filename != self.cur_file_name or started_context: + self.cur_file_name = filename + disp = self.should_trace_cache.get(filename) + if disp is None: + disp = self.should_trace(filename, frame) + self.should_trace_cache[filename] = disp + + self.cur_file_data = None + if disp.trace: + tracename = disp.source_filename + if tracename not in self.data: + self.data[tracename] = set() + self.cur_file_data = self.data[tracename] + else: + frame.f_trace_lines = False + elif not self.cur_file_data: + frame.f_trace_lines = False + # The call event is really a "start frame" event, and happens for # function calls and re-entering generators. The f_lasti field is # -1 for calls, and a real offset for generators. Use <0 as the @@ -174,6 +192,7 @@ self.last_line = -frame.f_code.co_firstlineno else: self.last_line = frame.f_lineno + elif event == 'line': # Record an executed line. if self.cur_file_data is not None: @@ -184,6 +203,7 @@ else: self.cur_file_data.add(lineno) self.last_line = lineno + elif event == 'return': if self.trace_arcs and self.cur_file_data: # Record an arc leaving the function, but beware that a @@ -211,6 +231,7 @@ if real_return: first = frame.f_code.co_firstlineno self.cur_file_data.add((self.last_line, -first)) + # Leaving this function, pop the filename stack. self.cur_file_data, self.cur_file_name, self.last_line, self.started_context = ( self.data_stack.pop() @@ -219,7 +240,7 @@ if self.started_context: self.context = None self.switch_context(None) - return self._trace + return self._cached_bound_method_trace def start(self): """Start this Tracer. @@ -237,10 +258,10 @@ # function, but we are marked as running again, so maybe it # will be ok? #self.log("~", "starting on different threads") - return self._trace + return self._cached_bound_method_trace - sys.settrace(self._trace) - return self._trace + sys.settrace(self._cached_bound_method_trace) + return self._cached_bound_method_trace def stop(self): """Stop this Tracer.""" @@ -265,9 +286,10 @@ # so don't warn if we are in atexit on PyPy and the trace function # has changed to None. dont_warn = (env.PYPY and env.PYPYVERSION >= (5, 4) and self.in_atexit and tf is None) - if (not dont_warn) and tf != self._trace: # pylint: disable=comparison-with-callable + if (not dont_warn) and tf != self._cached_bound_method_trace: # pylint: disable=comparison-with-callable self.warn( - f"Trace function changed, data is likely wrong: {tf!r} != {self._trace!r}", + "Trace function changed, data is likely wrong: " + + f"{tf!r} != {self._cached_bound_method_trace!r}", slug="trace-changed", )