Skip to content

Commit fbb3f63

Browse files
committed
perf: release the Python GIL around heavy TTTR/CLSM/Correlator calls
Add a reusable, exception-safe TTTRLIB_NOGIL SWIG macro (RAII tttrlib_gil_release guard wrapping $action, preserving the project's std::exception -> Python translation) and apply it to the heavy, Python-object-free entry points: - TTTR file loading: read_file, read_records, read_hdf_file, read_sm_file, and the reading constructors - CLSMImage::fill and the filling constructor - Correlator::run These run with the GIL released so other Python threads progress while a file loads or an image fills (both OpenMP-parallelized internally); results are only returned once the operation completes. numpy typemaps marshal under the GIL before/after $action, so only the pure C++ core executes GIL-free. Pda::evaluate is deliberately excluded because its director callback would re-enter Python without the GIL.
1 parent f8a709d commit fbb3f63

4 files changed

Lines changed: 48 additions & 0 deletions

File tree

ext/python/CLSM.i

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ static int myErr = 0; // flag to save error state
4949

5050
%shared_ptr(CLSMImage)
5151

52+
// Release the Python GIL around the (OpenMP-parallelized) image fill, which
53+
// touches no Python objects. Results are only returned once the fill completes.
54+
TTTRLIB_NOGIL(CLSMImage::fill) // src/CLSMImage.cpp:1394
55+
TTTRLIB_NOGIL(CLSMImage::CLSMImage) // filling constructor calls fill() (src/CLSMImage.cpp:641)
56+
5257
%include "CLSMPixel.h"
5358
%include "CLSMLine.h"
5459
%include "CLSMFrame.h"

ext/python/Correlator.i

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@
2626

2727
%include "CorrelatorPhotonStream.h"
2828
%include "CorrelatorCurve.h"
29+
// Release the Python GIL around the heavy correlation compute (internal C++
30+
// vectors only; no Python objects touched).
31+
TTTRLIB_NOGIL(Correlator::run) // src/Correlator.cpp:84
32+
2933
%include "Correlator.h"
3034

3135
%extend Correlator{

ext/python/TTTR.i

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,15 @@
4141
%apply (signed char * IN_ARRAY1, int DIM1) {(signed char *routing_channels, int n_routing_channels)}
4242
%apply (signed char * IN_ARRAY1, int DIM1) {(signed char *event_types, int n_event_types)}
4343

44+
// Release the Python GIL around heavy, Python-object-free file I/O so other
45+
// threads can run while a file is loaded. numpy typemaps marshal under the GIL
46+
// before/after $action; only the C++ read executes GIL-free.
47+
TTTRLIB_NOGIL(TTTR::read_file) // src/TTTR.cpp:494 — pure C file I/O
48+
TTTRLIB_NOGIL(TTTR::read_records) // all overloads, src/TTTR.cpp:796+
49+
TTTRLIB_NOGIL(TTTR::read_hdf_file) // src/TTTR.cpp:282
50+
TTTRLIB_NOGIL(TTTR::read_sm_file) // src/TTTR.cpp:392
51+
TTTRLIB_NOGIL(TTTR::TTTR) // reading constructors call read_file()
52+
4453
%include "TTTR.h"
4554

4655
%extend TTTR{%pythoncode "./ext/python/TTTR.py"}

ext/python/tttrlib.i

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,36 @@ from __future__ import annotations
2525
#include <assert.h>
2626
%}
2727

28+
%{
29+
// RAII: release the Python GIL for the scope's lifetime and re-acquire it on
30+
// destruction — including during C++ exception unwinding, so the catch handlers
31+
// in TTTRLIB_NOGIL (which call the Python C-API via SWIG_exception) run with the
32+
// GIL held.
33+
struct tttrlib_gil_release {
34+
PyThreadState *_save;
35+
tttrlib_gil_release() { _save = PyEval_SaveThread(); }
36+
~tttrlib_gil_release() { PyEval_RestoreThread(_save); }
37+
};
38+
%}
39+
40+
// Release the GIL around a heavy, Python-object-free method while preserving the
41+
// project's standard std::exception -> Python exception translation (see the
42+
// global %exception in MicrotimeLinearization.i). Apply before the header %include.
43+
%define TTTRLIB_NOGIL(Method)
44+
%exception Method {
45+
try {
46+
tttrlib_gil_release _gil_guard;
47+
$action
48+
} catch (const std::invalid_argument& e) {
49+
SWIG_exception(SWIG_ValueError, e.what());
50+
} catch (const std::exception& e) {
51+
SWIG_exception(SWIG_RuntimeError, e.what());
52+
} catch (...) {
53+
SWIG_exception(SWIG_UnknownError, "Unknown exception");
54+
}
55+
}
56+
%enddef
57+
2858
// Keep SWIG output quiet by default (runtime verbosity is controlled via TTTRLIB_VERBOSE).
2959
// Warning 302: Identifier redefined (ignored) (Renamed from 'pair< std::shared_ptr< TTTR >,std::shared_ptr< TTTR > >'),
3060
// Warning 389: operator[] ignored (consider using %extend)

0 commit comments

Comments
 (0)