@@ -253,6 +253,9 @@ cdef class LineProfiler:
253253 cdef public double timer_unit
254254 cdef public object threaddata
255255
256+ # This is shared between instances and threads
257+ _all_active_instances = {}
258+
256259 def __init__ (self , *functions ):
257260 self .functions = []
258261 self .code_hash_map = {}
@@ -319,6 +322,16 @@ cdef class LineProfiler:
319322 def __set__ (self , value ):
320323 self .threaddata.enable_count = value
321324
325+ # This is shared between instances, but thread-local
326+ property _active_instances :
327+ def __get__ (self ):
328+ thread_id = threading.get_ident()
329+ try :
330+ return self ._all_active_instances[thread_id]
331+ except KeyError :
332+ insts = self ._all_active_instances[thread_id] = set ()
333+ return insts
334+
322335 def enable_by_count (self ):
323336 """ Enable the profiler if it hasn't been enabled before.
324337 """
@@ -345,8 +358,11 @@ cdef class LineProfiler:
345358 # Register `line_profiler` with `sys.monitoring` in Python 3.12
346359 # and above;
347360 # see: https://docs.python.org/3/library/sys.monitoring.html
348- _sys_monitoring_register()
349- PyEval_SetTrace(python_trace_callback, self )
361+ instances = self ._active_instances
362+ if not instances:
363+ _sys_monitoring_register()
364+ PyEval_SetTrace(python_trace_callback, instances)
365+ instances.add(self )
350366
351367 @property
352368 def c_code_map (self ):
@@ -397,12 +413,15 @@ cdef class LineProfiler:
397413
398414
399415 cpdef disable(self ):
416+ instances = self ._active_instances
400417 self ._c_last_time[threading.get_ident()].clear()
401- unset_trace()
402- # Deregister `line_profiler` with `sys.monitoring` in Python
403- # 3.12 and above;
404- # see: https://docs.python.org/3/library/sys.monitoring.html
405- _sys_monitoring_deregister()
418+ instances.discard(self )
419+ if not instances:
420+ unset_trace()
421+ # Deregister `line_profiler` with `sys.monitoring` in Python
422+ # 3.12 and above;
423+ # see: https://docs.python.org/3/library/sys.monitoring.html
424+ _sys_monitoring_deregister()
406425
407426 def get_stats (self ):
408427 """
@@ -443,53 +462,59 @@ cdef class LineProfiler:
443462
444463@ cython.boundscheck (False )
445464@ cython.wraparound (False )
446- cdef extern int python_trace_callback(object self_, PyFrameObject * py_frame,
465+ cdef extern int python_trace_callback(object instances,
466+ PyFrameObject * py_frame,
447467 int what, PyObject * arg):
448468 """
449469 The PyEval_SetTrace() callback.
450470
451471 References:
452472 https://github.com/python/cpython/blob/de2a4036/Include/cpython/pystate.h#L16
453473 """
454- cdef LineProfiler self
474+ cdef object prof_
475+ cdef LineProfiler prof
455476 cdef object code
456477 cdef LineTime entry
457478 cdef LastTime old
458479 cdef int key
459480 cdef PY_LONG_LONG time
481+ cdef int has_time = 0
460482 cdef int64 code_hash
461483 cdef int64 block_hash
462484 cdef unordered_map[int64, LineTime] line_entries
463485 cdef uint64 linenum
464486
465- self = < LineProfiler> self_
466-
467487 if what == PyTrace_LINE or what == PyTrace_RETURN:
468488 # Normally we'd need to DECREF the return from get_frame_code, but Cython does that for us
469489 block_hash = hash (get_frame_code(py_frame))
470490
471491 linenum = PyFrame_GetLineNumber(py_frame)
472492 code_hash = compute_line_hash(block_hash, linenum)
473493
474- if self ._c_code_map.count(code_hash):
475- time = hpTimer()
494+ for prof_ in instances:
495+ prof = < LineProfiler> prof_
496+ if not prof._c_code_map.count(code_hash):
497+ continue
498+ if not has_time:
499+ time = hpTimer()
500+ has_time = 1
476501 ident = threading.get_ident()
477- if self ._c_last_time[ident].count(block_hash):
478- old = self ._c_last_time[ident][block_hash]
479- line_entries = self ._c_code_map[code_hash]
502+ if prof ._c_last_time[ident].count(block_hash):
503+ old = prof ._c_last_time[ident][block_hash]
504+ line_entries = prof ._c_code_map[code_hash]
480505 key = old.f_lineno
481506 if not line_entries.count(key):
482- self ._c_code_map[code_hash][key] = LineTime(code_hash, key, 0 , 0 )
483- self ._c_code_map[code_hash][key].nhits += 1
484- self ._c_code_map[code_hash][key].total_time += time - old.time
507+ prof ._c_code_map[code_hash][key] = LineTime(code_hash, key, 0 , 0 )
508+ prof ._c_code_map[code_hash][key].nhits += 1
509+ prof ._c_code_map[code_hash][key].total_time += time - old.time
485510 if what == PyTrace_LINE:
486511 # Get the time again. This way, we don't record much time wasted
487512 # in this function.
488- self ._c_last_time[ident][block_hash] = LastTime(linenum, hpTimer())
489- elif self ._c_last_time[ident].count(block_hash):
513+ prof ._c_last_time[ident][block_hash] = LastTime(linenum, hpTimer())
514+ elif prof ._c_last_time[ident].count(block_hash):
490515 # We are returning from a function, not executing a line. Delete
491516 # the last_time record. It may have already been deleted if we
492517 # are profiling a generator that is being pumped past its end.
493- self ._c_last_time[ident].erase(self ._c_last_time[ident].find(block_hash))
518+ prof ._c_last_time[ident].erase(prof ._c_last_time[ident].find(block_hash))
494519
495520 return 0
0 commit comments