Performance and guards
Pyccolo instrumentation adds significant overhead to Python. In many cases the overhead can be partially mitigated: if, for example, you only need instrumentation the first time a statement runs, you can deactivate it afterwards so that further calls (or loop iterations) execute uninstrumented, with all the mighty performance of native Python.
This is implemented with guards — runtime flags associated with a function or loop that gate whether its instrumentation fires. Activating a guard turns the instrumented region off; deactivating it turns it back on.
import pyccolo as pyc
class TracesOnce(pyc.BaseTracer):
@pyc.register_raw_handler((pyc.after_for_loop_iter, pyc.after_while_loop_iter))
def after_loop_iter(self, *_, guard, **__):
self.activate_guard(guard)
@pyc.register_raw_handler(pyc.after_function_execution)
def after_function_exec(self, *_, guard, **__):
self.activate_guard(guard)
Here, after the first loop iteration (respectively, the first time a function
finishes executing), the associated guard is activated, so subsequent iterations
and calls run without instrumentation. Note the use of
pyccolo.register_raw_handler(): guard-bearing events pass the guard name as
the guard keyword argument, and the raw registrar is the form that surfaces
it.
Subsequent calls / iterations will be instrumented again only after calling
self.deactivate_guard(...) on the
associated function / loop guard.
Guards are one of the levers that make heavyweight dynamic analyses practical on real workloads: pay for instrumentation exactly where and when you need the signal, and run native everywhere else.