Profiler for timing all aspects of a workflow. This includes using stack tracing to capture call times for all selected calls (by default calls to `Transform.__call__` methods), times within context blocks, times to generate items from iterables, and times to execute decorated functions
| 152 | |
| 153 | |
| 154 | class WorkflowProfiler: |
| 155 | """ |
| 156 | Profiler for timing all aspects of a workflow. This includes using stack tracing to capture call times for |
| 157 | all selected calls (by default calls to `Transform.__call__` methods), times within context blocks, times |
| 158 | to generate items from iterables, and times to execute decorated functions. |
| 159 | |
| 160 | This profiler must be used only within its context because it uses an internal thread to read results from a |
| 161 | multiprocessing queue. This allows the profiler to function across multiple threads and processes, though the |
| 162 | multiprocess tracing is at times unreliable and not available in Windows at all. |
| 163 | |
| 164 | The profiler uses `sys.settrace` and `threading.settrace` to find all calls to profile, this will be set when |
| 165 | the context enters and cleared when it exits so proper use of the context is essential to prevent excessive |
| 166 | tracing. Note that tracing has a high overhead so times will not accurately reflect real world performance |
| 167 | but give an idea of relative share of time spent. |
| 168 | |
| 169 | The tracing functionality uses a selector to choose which calls to trace, since tracing all calls induces |
| 170 | infinite loops and would be terribly slow even if not. This selector is a callable accepting a `call` trace |
| 171 | frame and returns True if the call should be traced. The default is `select_transform_call` which will return |
| 172 | True for `Transform.__call__` calls only. |
| 173 | |
| 174 | Example showing use of all profiling functions: |
| 175 | |
| 176 | .. code-block:: python |
| 177 | |
| 178 | import monai.transform as mt |
| 179 | from monai.utils import WorkflowProfiler |
| 180 | import torch |
| 181 | |
| 182 | comp=mt.Compose([mt.ScaleIntensity(),mt.RandAxisFlip(0.5)]) |
| 183 | |
| 184 | with WorkflowProfiler() as wp: |
| 185 | for _ in wp.profile_iter("range",range(5)): |
| 186 | with wp.profile_ctx("Loop"): |
| 187 | for i in range(10): |
| 188 | comp(torch.rand(1,16,16)) |
| 189 | |
| 190 | @wp.profile_callable() |
| 191 | def foo(): pass |
| 192 | |
| 193 | foo() |
| 194 | foo() |
| 195 | |
| 196 | print(wp.get_times_summary_pd()) # print results |
| 197 | |
| 198 | Args: |
| 199 | call_selector: selector to determine which calls to trace, use None to disable tracing |
| 200 | """ |
| 201 | |
| 202 | def __init__(self, call_selector=select_transform_call): |
| 203 | self.results = defaultdict(list) |
| 204 | self.parent_pid = os.getpid() |
| 205 | self.read_thread: threading.Thread | None = None |
| 206 | self.lock = threading.RLock() |
| 207 | self.queue: multiprocessing.SimpleQueue = multiprocessing.SimpleQueue() |
| 208 | self.queue_timeout = 0.1 |
| 209 | self.call_selector = call_selector |
| 210 | |
| 211 | def _is_parent(self): |
no outgoing calls
searching dependent graphs…