Skip to content

Commit f82658e

Browse files
committed
Remove jl_call_nogil and benchmark regions
1 parent 6625707 commit f82658e

7 files changed

Lines changed: 68 additions & 31 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
to relinquish Python resources around yielding or blocking Julia code.
2323
* JuliaCall now relinquishes and restores Python resources automatically while running
2424
Julia code, including Python-to-Julia callbacks.
25+
* Removed JuliaCall's `jl_call_nogil` method; ordinary calls now provide its behavior.
2526
* Python-object finalizers no longer attach a Python thread state. Pending decrefs are
2627
queued until a state is already attached or `PythonCall.GC.gc()` drains the queue.
2728
* Changes to `PythonCall.GC` (now more like `Base.GC`):

‎benchmark/benchmarks.jl‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,49 @@ end
5050
SUITE["basic"]["@py"]["pydict"]["init"] = @benchmarkable test_atpy(Val(false))
5151
SUITE["basic"]["@py"]["pydict"]["pydel"] = @benchmarkable test_atpy(Val(true))
5252

53+
function test_region_pythoncall(x, n)
54+
ans = 0
55+
for _ = 1:n
56+
ans += pylen(x)
57+
end
58+
return ans
59+
end
60+
61+
function test_region_pythoncall_outer(x, n)
62+
@pyregion begin
63+
ans = 0
64+
for _ = 1:n
65+
ans += pylen(x)
66+
end
67+
return ans
68+
end
69+
end
70+
71+
function test_region_capi(x, n)
72+
@pyregion begin
73+
ans = 0
74+
for _ = 1:n
75+
ans += PythonCall.C.PyObject_Length(x)
76+
end
77+
return ans
78+
end
79+
end
80+
81+
const REGION_BENCHMARK_LENGTH = 1000
82+
83+
SUITE["region"]["pythoncall"] = @benchmarkable(
84+
test_region_pythoncall(x, REGION_BENCHMARK_LENGTH),
85+
setup=(x = pytuple((1, 2, 3))),
86+
)
87+
SUITE["region"]["pythoncall_outer"] = @benchmarkable(
88+
test_region_pythoncall_outer(x, REGION_BENCHMARK_LENGTH),
89+
setup=(x = pytuple((1, 2, 3))),
90+
)
91+
SUITE["region"]["capi_outer"] = @benchmarkable(
92+
test_region_capi(x, REGION_BENCHMARK_LENGTH),
93+
setup=(x = pytuple((1, 2, 3))),
94+
)
95+
5396

5497
include("gcbench.jl")
5598
using .GCBench: append_lots

‎docs/src/juliacall-reference.md‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,6 @@ and ending with `__` (since these are Python special methods) or starting with `
7070
- `jl_callback(*args, **kwargs)`: Calls the Julia object with the given arguments.
7171
Unlike ordinary calling syntax, the arguments are passed as `Py` objects instead of
7272
being converted.
73-
- `jl_call_nogil(*args, **kwargs)`: Compatibility spelling for an ordinary call. Python
74-
resources are relinquished automatically while Julia code runs.
7573
- `jl_display()`: Display the object using Julia's display mechanism.
7674
- `jl_eval(expr)`: If the object is a Julia `Module`, evaluates the given expression.
7775
- `jl_help()`: Display help for the object.

‎docs/src/juliacall.md‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,24 @@ PythonCall operations nested inside Julia callbacks are safe without explicit re
171171

172172
You can also use [multi-threading from Julia](@ref jl-multi-threading).
173173

174+
### Caveat: Julia's task scheduler
175+
176+
If you use a Julia function which yields to the task scheduler, such as `sleep` instead of
177+
`Libc.systemsleep` in the example above, the Python thread waiting for the futures must
178+
periodically yield to Julia so that the Julia tasks can finish:
179+
180+
```python
181+
jl_yield = getattr(jl, "yield")
182+
while True:
183+
jl_yield()
184+
state = wait(fs, timeout=0.1)
185+
if not state.not_done:
186+
break
187+
```
188+
189+
This scheduler requirement is independent of Python thread-state management; ordinary
190+
JuliaCall calls still relinquish and restore Python resources automatically.
191+
174192
### [Caveat: Signal handling](@id py-multi-threading-signal-handling)
175193

176194
We recommend setting [`PYTHON_JULIACALL_HANDLE_SIGNALS=yes`](@ref julia-config)

‎docs/src/v1-migration-guide.md‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,8 @@ The outer `@pyregion` can also be omitted; the PythonCall operations remain corr
117117
`wait(event)` then needs no annotation. Both macros nest safely.
118118

119119
JuliaCall similarly relinquishes Python resources automatically while arbitrary Julia code
120-
runs. Ordinary calls should be used instead of the old `_jl_call_nogil` spelling. The v1
121-
`jl_call_nogil` method remains available for compatibility but behaves like an ordinary
122-
call.
120+
runs. The `_jl_call_nogil` and `jl_call_nogil` methods have been removed; use an ordinary
121+
call instead.
123122

124123
Finalizers no longer attach a Python thread state and block waiting for Python. If prompt
125124
cleanup matters, call `PythonCall.GC.gc()` at a suitable point to drain queued decrefs.

‎pytest/test_all.py‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,20 +113,20 @@ def test_julia_gc():
113113

114114

115115
@pytest.mark.parametrize("yld", [True, False])
116-
def test_call_nogil(yld):
117-
"""Tests that we can execute Julia code in parallel by releasing the GIL."""
116+
def test_parallel_call(yld):
117+
"""Tests that ordinary calls execute Julia code in parallel."""
118118
from concurrent.futures import ThreadPoolExecutor, wait
119119
from time import time
120120
from juliacall import Main as jl
121121

122-
# julia implementation of sleep which unlocks the GIL
122+
# Julia implementations of sleep which do and do not yield.
123123
if yld:
124124
# use sleep, which yields
125125
jsleep = jl.sleep
126126
else:
127127
# use Libc.systemsleep which does not yield
128128
jsleep = jl.Libc.systemsleep
129-
jsleep = jsleep.jl_call_nogil
129+
assert not hasattr(jsleep, "jl_call_nogil")
130130
jyield = getattr(jl, "yield")
131131
# precompile
132132
jsleep(0.01)

‎src/JlWrap/any.jl‎

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -90,24 +90,6 @@ end
9090
pyjl_handle_error_type(::typeof(pyjlany_callback), self, exc::MethodError) =
9191
exc.f === self ? pybuiltins.TypeError : PyNULL
9292

93-
function pyjlany_call_nogil(self, args_::Py, kwargs_::Py)
94-
if pylen(kwargs_) > 0
95-
args = pyconvert(Vector{Any}, args_)
96-
kwargs = pyconvert(Dict{Symbol,Any}, kwargs_)
97-
ans = pyjl(self(args...; kwargs...))
98-
elseif pylen(args_) > 0
99-
args = pyconvert(Vector{Any}, args_)
100-
ans = pyjl(self(args...))
101-
else
102-
ans = pyjl(self())
103-
end
104-
unsafe_pydel(args_)
105-
unsafe_pydel(kwargs_)
106-
ans
107-
end
108-
pyjl_handle_error_type(::typeof(pyjlany_call_nogil), self, exc::MethodError) =
109-
exc.f === self ? pybuiltins.TypeError : PyNULL
110-
11193
function pyjlany_getitem(self, k_::Py)
11294
if self isa Type
11395
if pyistuple(k_)
@@ -607,10 +589,6 @@ class Jl(JlBase2):
607589
return self._jl_callmethod($(pyjl_methodnum(Py)))
608590
def jl_callback(self, *args, **kwargs):
609591
return self._jl_callmethod($(pyjl_methodnum(pyjlany_callback)), args, kwargs)
610-
def jl_call_nogil(self, *args, **kwargs):
611-
'''Compatibility alias for calling this Julia object. Python resources are
612-
relinquished automatically while Julia code runs.'''
613-
return self._jl_callmethod($(pyjl_methodnum(pyjlany_call_nogil)), args, kwargs)
614592
def _repr_mimebundle_(self, include=None, exclude=None):
615593
return self._jl_callmethod($(pyjl_methodnum(pyjlany_mimebundle)), include, exclude)
616594
@property

0 commit comments

Comments
 (0)