-
-
Notifications
You must be signed in to change notification settings - Fork 11.3k
ENH: add support in f2py to declare gil-disabled support #26981
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Thanks @ngoldbaum! @HaoZeke you may want to have a quick peek at the new CLI flag to see you're happy with it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the ping @rgommers this looks good but it would be nicer to rework the added argument into the existing argparse
methods used in run_compile
instead, @ngoldbaum.
Basically adding to the logic in here:
Lines 553 to 587 in 472b938
def make_f2py_compile_parser(): | |
parser = argparse.ArgumentParser(add_help=False) | |
parser.add_argument("--dep", action="append", dest="dependencies") | |
parser.add_argument("--backend", choices=['meson', 'distutils'], default='distutils') | |
parser.add_argument("-m", dest="module_name") | |
return parser | |
def preparse_sysargv(): | |
# To keep backwards bug compatibility, newer flags are handled by argparse, | |
# and `sys.argv` is passed to the rest of `f2py` as is. | |
parser = make_f2py_compile_parser() | |
args, remaining_argv = parser.parse_known_args() | |
sys.argv = [sys.argv[0]] + remaining_argv | |
backend_key = args.backend | |
if MESON_ONLY_VER and backend_key == 'distutils': | |
outmess("Cannot use distutils backend with Python>=3.12," | |
" using meson backend instead.\n") | |
backend_key = "meson" | |
return { | |
"dependencies": args.dependencies or [], | |
"backend": backend_key, | |
"modulename": args.module_name, | |
} | |
def run_compile(): | |
""" | |
Do it all in one call! | |
""" | |
import tempfile | |
# Collect dependency flags, preprocess sys.argv | |
argy = preparse_sysargv() |
doc/source/f2py/usage.rst
Outdated
is ``--requires-gil`` for backwards compatibility. Inspect the fortran | ||
code you are wrapping for thread safety issues before passing | ||
``--no-requires-gil``, as ``f2py`` does not analyze fortran code for thread | ||
safety issues. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There were two things that came up on the other PR. One was the idea to align with cython and use --freethreading-compatible
, not sure about the merit, but as such aligning sounds like a good idea.
The other was that existing threadsafe
markers are already sufficient to mark a module as compatible if all functions are marked.
So unless you actually don't like GIL release (due to overhead?), in all cases where you pass --no-requires-gil
it makes sense to also mark all functions as threadsafe
for the GIL enabled builds.
(If you do pass --requires-gil
you will get a RuntimeWarning
either way, I think; the downside being that I suppose that runtime warning will tell you the module, but less about how to adapt.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I looked at this today and it's a little more complicated than just assuming that if the GIL is disabled, we can mark functions as threadsafe
using the f2py statement. In particular, the handling for callbacks uses the PyThreadState
C API, which cannot be accessed inside Py_BEGIN_ALLOW_THREADS
blocks. In practice this means that python dies during the callback tests because the C API detects that it's in a Py_BEGIN_ALLOW_THREADS
block.
My understanding is that in the free-threaded build, the adding the Py_BEGIN_ALLOW_THREADS/Py_END_ALLOW_THREADS
would be an optimization (in the free-threaded build, it makes threads go into DETACHED state, which makes it a little easier to stop the world for a GC pass), but is not required.
I could restrict adding the macros to code that doesn't use callbacks, but I'm also worried about adding threadsafe
to more wrapped fortran code, given at least as far as I can tell, it doesn't appear in any of the pyf
files in the f2py tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The other was that existing threadsafe markers are already sufficient to mark a module as compatible if all functions are marked.
I'll take a look at this though, this seems reasonable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I looked at SciPy, and there is only one module (the dfitpack
wrappers) where all the functions are marked thread safe. So I don't think it's worth the added complexity in the f2py code to do this, since users can just pass the CLI flag if they want to have free-threading support. I also think manually opting in isn't a bad idea either.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since users can just pass the CLI flag if they want to have free-threading support.
Right, but for the next few years at least it would be nice if you could conveniently get GIL release as well. This way you get free-threaded, I guess, but these should probably already release the GIL?
(have to come back to think about it a bit, maybe it all just doesn't matter much, but it seems like easy to run fully free, but surprsingly hard to release the GIL even though that is roughly the same for fortran code.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree in principle, but in practice the f2py wrappers are not written in a way that makes it easy to release the GIL, at least for the code that handles callbacks. I'd also be a little nervous about packages suddenly shipping fortran modules that release the GIL on GIL-enabled builds when they didn't do that before, just because the package added support for free-threaded builds. I agree that most of the time it will be fine, but up until now people have had to opt in. I may also be worrying about nothing, it just feels slightly risky to me given the stability of f2py in general.
The last push renames the command-line argument from |
@@ -615,7 +630,7 @@ def run_compile(): | |||
sysinfo_flags = [f[7:] for f in sysinfo_flags] | |||
|
|||
_reg2 = re.compile( | |||
r'--((no-|)(wrap-functions|lower)|debug-capi|quiet|skip-empty-wrappers)|-include') | |||
r'--((no-|)(wrap-functions|lower|freethreading-compatible)|debug-capi|quiet|skip-empty-wrappers)|-include') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
because of the argv munging that happens in this function, I also need to modify this regex, in addition to your suggestion @HaoZeke. Some of the tests only do something interesting on the free-threaded python build, which I'm guessing you don't have to test with.
Thanks for your suggestion, it didn't occur to me to just make get_includes
a more generic interface.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, thanks for updating the flags @ngoldbaum.
Thanks @ngoldbaum and @HaoZeke for the review. |
This adds two new flags to the f2py CLI:
--[no-]requires-gil
, defaulting to--requires-gil
. This allows f2py to generate modules that declare they support running without the GIL. I added tests and docs and can expand on those if anyone has suggestions.As far as the thread safety of the f2py wrapper code, I did an audit on the lapack wrappers in f2py and some test wrappers I generated based on the f2py tests. I didn't see any thread safety issues there. There is also already special handling to make sure callbacks are thread safe. See this gist where Ralf and I were looking at this.
This also adds some support in NumPy's tests to make sure the GIL never gets re-enabled during the tests. Happy to split off the testing changes from the f2py changes if they are controversial.
It uses
pytest.exit()
and when it fails it looks like this:In that example I forced the failure by updating to the
main
branch and applied theconftest.py
changes from this PR.So, no regular final pytest output about how many tests passed, failed, xpassed, etc, just a message saying
Exit: GIL re-enabled during tests
. Unfortunately I don't see a nicer way to fail the whole test suite after the tests are finished running using the pytest API, but maybe someone knows a better way?