Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
cython
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Gwenaël Samain
cython
Commits
1112a9cb
Commit
1112a9cb
authored
Feb 26, 2019
by
Stefan Behnel
Committed by
GitHub
Feb 26, 2019
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #2860 from noamher/conditional-gilstatnode
Allow condition in GILStatNode
parents
b07ee506
5dc832d3
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
456 additions
and
2 deletions
+456
-2
Cython/Compiler/Nodes.py
Cython/Compiler/Nodes.py
+10
-1
Cython/Compiler/Optimize.py
Cython/Compiler/Optimize.py
+24
-0
Cython/Compiler/ParseTreeTransforms.py
Cython/Compiler/ParseTreeTransforms.py
+13
-0
Cython/Compiler/Parsing.py
Cython/Compiler/Parsing.py
+9
-1
docs/src/userguide/external_C_code.rst
docs/src/userguide/external_C_code.rst
+20
-0
docs/src/userguide/fusedtypes.rst
docs/src/userguide/fusedtypes.rst
+28
-0
tests/errors/nogil_conditional.pyx
tests/errors/nogil_conditional.pyx
+81
-0
tests/run/nogil_conditional.pyx
tests/run/nogil_conditional.pyx
+271
-0
No files found.
Cython/Compiler/Nodes.py
View file @
1112a9cb
...
@@ -7805,10 +7805,12 @@ class GILStatNode(NogilTryFinallyStatNode):
...
@@ -7805,10 +7805,12 @@ class GILStatNode(NogilTryFinallyStatNode):
#
#
# state string 'gil' or 'nogil'
# state string 'gil' or 'nogil'
child_attrs
=
[
"condition"
]
+
NogilTryFinallyStatNode
.
child_attrs
state_temp
=
None
state_temp
=
None
def
__init__
(
self
,
pos
,
state
,
body
):
def
__init__
(
self
,
pos
,
state
,
body
,
condition
=
None
):
self
.
state
=
state
self
.
state
=
state
self
.
condition
=
condition
self
.
create_state_temp_if_needed
(
pos
,
state
,
body
)
self
.
create_state_temp_if_needed
(
pos
,
state
,
body
)
TryFinallyStatNode
.
__init__
(
TryFinallyStatNode
.
__init__
(
self
,
pos
,
self
,
pos
,
...
@@ -7835,11 +7837,18 @@ class GILStatNode(NogilTryFinallyStatNode):
...
@@ -7835,11 +7837,18 @@ class GILStatNode(NogilTryFinallyStatNode):
if
self
.
state
==
'gil'
:
if
self
.
state
==
'gil'
:
env
.
has_with_gil_block
=
True
env
.
has_with_gil_block
=
True
if
self
.
condition
is
not
None
:
self
.
condition
.
analyse_declarations
(
env
)
return
super
(
GILStatNode
,
self
).
analyse_declarations
(
env
)
return
super
(
GILStatNode
,
self
).
analyse_declarations
(
env
)
def
analyse_expressions
(
self
,
env
):
def
analyse_expressions
(
self
,
env
):
env
.
use_utility_code
(
env
.
use_utility_code
(
UtilityCode
.
load_cached
(
"ForceInitThreads"
,
"ModuleSetupCode.c"
))
UtilityCode
.
load_cached
(
"ForceInitThreads"
,
"ModuleSetupCode.c"
))
if
self
.
condition
is
not
None
:
self
.
condition
=
self
.
condition
.
analyse_expressions
(
env
)
was_nogil
=
env
.
nogil
was_nogil
=
env
.
nogil
env
.
nogil
=
self
.
state
==
'nogil'
env
.
nogil
=
self
.
state
==
'nogil'
node
=
TryFinallyStatNode
.
analyse_expressions
(
self
,
env
)
node
=
TryFinallyStatNode
.
analyse_expressions
(
self
,
env
)
...
...
Cython/Compiler/Optimize.py
View file @
1112a9cb
...
@@ -4688,6 +4688,30 @@ class ConstantFolding(Visitor.VisitorTransform, SkipDeclarations):
...
@@ -4688,6 +4688,30 @@ class ConstantFolding(Visitor.VisitorTransform, SkipDeclarations):
return
None
return
None
return
node
return
node
def
visit_GILStatNode
(
self
,
node
):
self
.
visitchildren
(
node
)
if
node
.
condition
is
None
:
return
node
if
node
.
condition
.
has_constant_result
():
# Condition is True - Modify node to be a normal
# GILStatNode with condition=None
if
node
.
condition
.
constant_result
:
node
.
condition
=
None
# Condition is False - the body of the GILStatNode
# should run without changing the state of the gil
# return the body of the GILStatNode
else
:
return
node
.
body
# If condition is not constant we keep the GILStatNode as it is.
# Either it will later become constant (e.g. a `numeric is int`
# expression in a fused type function) and then when ConstantFolding
# runs again it will be handled or a later transform (i.e. GilCheck)
# will raise an error
return
node
# in the future, other nodes can have their own handler method here
# in the future, other nodes can have their own handler method here
# that can replace them with a constant result node
# that can replace them with a constant result node
...
...
Cython/Compiler/ParseTreeTransforms.py
View file @
1112a9cb
...
@@ -2915,6 +2915,12 @@ class GilCheck(VisitorTransform):
...
@@ -2915,6 +2915,12 @@ class GilCheck(VisitorTransform):
return
node
return
node
def
visit_GILStatNode
(
self
,
node
):
def
visit_GILStatNode
(
self
,
node
):
if
node
.
condition
is
not
None
:
error
(
node
.
condition
.
pos
,
"Non-constant condition in a "
"`with %s(<condition>)` statement"
%
node
.
state
)
return
node
if
self
.
nogil
and
node
.
nogil_check
:
if
self
.
nogil
and
node
.
nogil_check
:
node
.
nogil_check
()
node
.
nogil_check
()
...
@@ -3254,6 +3260,13 @@ class ReplaceFusedTypeChecks(VisitorTransform):
...
@@ -3254,6 +3260,13 @@ class ReplaceFusedTypeChecks(VisitorTransform):
self
.
visitchildren
(
node
)
self
.
visitchildren
(
node
)
return
self
.
transform
(
node
)
return
self
.
transform
(
node
)
def
visit_GILStatNode
(
self
,
node
):
"""
Fold constant condition of GILStatNode.
"""
self
.
visitchildren
(
node
)
return
self
.
transform
(
node
)
def
visit_PrimaryCmpNode
(
self
,
node
):
def
visit_PrimaryCmpNode
(
self
,
node
):
with
Errors
.
local_errors
(
ignore
=
True
):
with
Errors
.
local_errors
(
ignore
=
True
):
type1
=
node
.
operand1
.
analyse_as_type
(
self
.
local_scope
)
type1
=
node
.
operand1
.
analyse_as_type
(
self
.
local_scope
)
...
...
Cython/Compiler/Parsing.py
View file @
1112a9cb
...
@@ -2051,12 +2051,20 @@ def p_with_items(s, is_async=False):
...
@@ -2051,12 +2051,20 @@ def p_with_items(s, is_async=False):
s
.
error
(
"with gil/nogil cannot be async"
)
s
.
error
(
"with gil/nogil cannot be async"
)
state
=
s
.
systring
state
=
s
.
systring
s
.
next
()
s
.
next
()
# support conditional gil/nogil
condition
=
None
if
s
.
sy
==
'('
:
s
.
next
()
condition
=
p_test
(
s
)
s
.
expect
(
')'
)
if
s
.
sy
==
','
:
if
s
.
sy
==
','
:
s
.
next
()
s
.
next
()
body
=
p_with_items
(
s
)
body
=
p_with_items
(
s
)
else
:
else
:
body
=
p_suite
(
s
)
body
=
p_suite
(
s
)
return
Nodes
.
GILStatNode
(
pos
,
state
=
state
,
body
=
body
)
return
Nodes
.
GILStatNode
(
pos
,
state
=
state
,
body
=
body
,
condition
=
condition
)
else
:
else
:
manager
=
p_test
(
s
)
manager
=
p_test
(
s
)
target
=
None
target
=
None
...
...
docs/src/userguide/external_C_code.rst
View file @
1112a9cb
...
@@ -580,6 +580,26 @@ The GIL may also be acquired through the ``with gil`` statement::
...
@@ -580,6 +580,26 @@ The GIL may also be acquired through the ``with gil`` statement::
with gil:
with gil:
<execute this block with the GIL acquired>
<execute this block with the GIL acquired>
.. _gil_conditional:
Conditional Acquiring / Releasing the GIL
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Sometimes it is helpful to use a condition to decide whether to run a
certain piece of code with or without the GIL. This code would run anyway,
the difference is whether the GIL will be held or released.
The condition must be constant (at compile time).
This could be useful for profiling, debugging, performance testing, and
for fused types (see :ref:`fused_gil_conditional`).::
DEF FREE_GIL = True
with nogil(FREE_GIL):
<code to be executed with the GIL released>
with gil(False):
<GIL is still released>
Declaring a function as callable without the GIL
Declaring a function as callable without the GIL
--------------------------------------------------
--------------------------------------------------
...
...
docs/src/userguide/fusedtypes.rst
View file @
1112a9cb
...
@@ -249,6 +249,34 @@ to figure out whether a specialization is part of another set of types
...
@@ -249,6 +249,34 @@ to figure out whether a specialization is part of another set of types
if bunch_of_types in string_t:
if bunch_of_types in string_t:
print("s is a string!")
print("s is a string!")
.. _fused_gil_conditional:
Conditional GIL Acquiring / Releasing
=====================================
Acquiring and releasing the GIL can be controlled by a condition
which is known at compile time (see :ref:`_gil_conditional`).
This is most useful when combined with fused types.
A fused type function may have to handle both cython native types
(e.g. cython.int or cython.double) and python types (e.g. object or bytes).
Conditional Acquiring / Releasing the GIL provides a method for running
the same piece of code either with the GIL released (for cython native types)
and with the GIL held (for python types).::
cimport cython
ctypedef fused double_or_object:
cython.double
object
def increment(double_or_object x):
with nogil(double_or_object is cython.double):
# Same code handles both cython.double (GIL is released)
# and python object (GIL is not released).
x = x + 1
return x
__signatures__
__signatures__
==============
==============
...
...
tests/errors/nogil_conditional.pyx
0 → 100644
View file @
1112a9cb
# cython: remove_unreachable=False
# mode: error
cdef
int
f_nogil
(
int
x
)
nogil
:
cdef
int
y
y
=
x
+
10
return
y
def
f_gil
(
x
):
y
=
0
y
=
x
+
100
return
y
def
illegal_gil_usage
():
cdef
int
res
=
0
with
nogil
(
True
):
res
=
f_gil
(
res
)
with
nogil
(
True
):
res
=
f_gil
(
res
)
with
gil
(
False
):
res
=
f_gil
(
res
)
with
nogil
(
False
):
res
=
f_nogil
(
res
)
def
foo
(
a
):
return
a
<
10
def
non_constant_condition
(
int
x
)
->
int
:
cdef
int
res
=
x
with
nogil
(
x
<
10
):
res
=
f_nogil
(
res
)
with
gil
(
foo
(
x
)):
res
=
f_gil
(
res
)
ctypedef
fused
number_or_object
:
float
object
def
fused_type
(
number_or_object
x
):
with
nogil
(
number_or_object
is
object
):
res
=
x
+
1
# This should be fine
with
nogil
(
number_or_object
is
float
):
res
=
x
+
1
return
res
_ERRORS
=
u"""
19:14: Accessing Python global or builtin not allowed without gil
19:19: Calling gil-requiring function not allowed without gil
19:19: Coercion from Python not allowed without the GIL
19:19: Constructing Python tuple not allowed without gil
19:20: Converting to Python object not allowed without gil
21:13: Trying to release the GIL while it was previously released.
22:18: Accessing Python global or builtin not allowed without gil
22:23: Calling gil-requiring function not allowed without gil
22:23: Coercion from Python not allowed without the GIL
22:23: Constructing Python tuple not allowed without gil
22:24: Converting to Python object not allowed without gil
25:18: Accessing Python global or builtin not allowed without gil
25:23: Calling gil-requiring function not allowed without gil
25:23: Coercion from Python not allowed without the GIL
25:23: Constructing Python tuple not allowed without gil
25:24: Converting to Python object not allowed without gil
37:17: Non-constant condition in a `with nogil(<condition>)` statement
40:16: Non-constant condition in a `with gil(<condition>)` statement
51:8: Assignment of Python object not allowed without gil
51:16: Calling gil-requiring function not allowed without gil
"""
tests/run/nogil_conditional.pyx
0 → 100644
View file @
1112a9cb
# mode: run
try
:
from
StringIO
import
StringIO
except
ImportError
:
from
io
import
StringIO
def
test
(
int
x
):
"""
>>> test(0)
110
"""
with
nogil
(
True
):
x
=
f_nogil
(
x
)
with
gil
(
True
):
x
=
f_gil
(
x
)
return
x
cdef
int
f_nogil
(
int
x
)
nogil
:
cdef
int
y
y
=
x
+
10
return
y
def
f_gil
(
x
):
y
=
0
y
=
x
+
100
return
y
cdef
int
with_gil_func
()
except
?
-
1
with
gil
:
raise
Exception
(
"error!"
)
cdef
int
nogil_func
()
nogil
except
?
-
1
:
with_gil_func
()
def
test_nogil_exception_propagation
():
"""
>>> test_nogil_exception_propagation()
Traceback (most recent call last):
...
Exception: error!
"""
with
nogil
:
with
gil
:
with
nogil
(
True
):
nogil_func
()
cdef
int
write_unraisable
()
nogil
:
with
gil
:
raise
ValueError
()
def
test_unraisable
():
"""
>>> print(test_unraisable()) # doctest: +ELLIPSIS
ValueError
Exception...ignored...
"""
import
sys
old_stderr
=
sys
.
stderr
stderr
=
sys
.
stderr
=
StringIO
()
try
:
write_unraisable
()
finally
:
sys
.
stderr
=
old_stderr
return
stderr
.
getvalue
().
strip
()
def
test_nested
():
"""
>>> test_nested()
240
"""
cdef
int
res
=
0
with
nogil
(
True
):
res
=
f_nogil
(
res
)
with
gil
(
1
<
2
):
res
=
f_gil
(
res
)
with
nogil
:
res
=
f_nogil
(
res
)
with
gil
:
res
=
f_gil
(
res
)
with
nogil
(
True
):
res
=
f_nogil
(
res
)
with
nogil
:
res
=
f_nogil
(
res
)
return
res
DEF
FREE_GIL
=
True
DEF
FREE_GIL_FALSE
=
False
def
test_nested_condition_false
():
"""
>>> test_nested_condition_false()
220
"""
cdef
int
res
=
0
with
gil
(
FREE_GIL_FALSE
):
res
=
f_gil
(
res
)
with
nogil
(
False
):
res
=
f_gil
(
res
)
with
nogil
(
FREE_GIL
):
res
=
f_nogil
(
res
)
with
gil
(
False
):
res
=
f_nogil
(
res
)
return
res
def
test_try_finally
():
"""
>>> test_try_finally()
113
"""
cdef
int
res
=
0
try
:
with
nogil
(
True
):
try
:
res
=
f_nogil
(
res
)
with
gil
(
1
<
2
):
try
:
res
=
f_gil
(
res
)
finally
:
res
+=
1
finally
:
res
=
res
+
1
finally
:
res
+=
1
return
res
ctypedef
fused
number_or_object
:
int
float
object
def
test_fused
(
number_or_object
x
)
->
number_or_object
:
"""
>>> test_fused[int](1)
2
>>> test_fused[float](1.0)
2.0
>>> test_fused[object](1)
2
>>> test_fused[object](1.0)
2.0
"""
cdef
number_or_object
res
=
x
with
nogil
(
number_or_object
is
not
object
):
res
=
res
+
1
return
res
ctypedef
fused
int_or_object
:
int
object
def
test_fused_object
(
int_or_object
x
):
"""
>>> test_fused_object[object]("spam")
456
>>> test_fused_object[int](1000)
1000
"""
cdef
int
res
=
0
if
int_or_object
is
object
:
with
nogil
(
False
):
res
+=
len
(
x
)
try
:
with
nogil
(
int_or_object
is
object
):
try
:
with
gil
(
int_or_object
is
object
):
res
=
f_gil
(
res
)
with
gil
:
res
=
f_gil
(
res
)
with
gil
(
False
):
res
=
f_nogil
(
res
)
with
gil
(
int_or_object
is
not
object
):
res
=
f_nogil
(
res
)
with
nogil
(
False
):
res
=
f_nogil
(
res
)
res
=
f_nogil
(
res
)
finally
:
res
=
res
+
1
with
nogil
(
int_or_object
is
not
object
):
res
=
f_gil
(
res
)
with
gil
(
int_or_object
is
not
object
):
res
=
f_gil
(
res
)
with
nogil
(
int_or_object
is
object
):
res
=
f_nogil
(
res
)
finally
:
res
+=
1
else
:
res
=
x
return
res
def
test_fused_int
(
int_or_object
x
):
"""
>>> test_fused_int[object]("spam")
4
>>> test_fused_int[int](1000)
1452
"""
cdef
int
res
=
0
if
int_or_object
is
int
:
res
+=
x
try
:
with
nogil
(
int_or_object
is
int
):
try
:
with
gil
(
int_or_object
is
int
):
res
=
f_gil
(
res
)
with
gil
:
res
=
f_gil
(
res
)
with
gil
(
False
):
res
=
f_nogil
(
res
)
with
gil
(
int_or_object
is
not
int
):
res
=
f_nogil
(
res
)
with
nogil
(
False
):
res
=
f_nogil
(
res
)
res
=
f_nogil
(
res
)
finally
:
res
=
res
+
1
with
nogil
(
int_or_object
is
not
int
):
res
=
f_gil
(
res
)
with
gil
(
int_or_object
is
not
int
):
res
=
f_gil
(
res
)
with
nogil
(
int_or_object
is
int
):
res
=
f_nogil
(
res
)
finally
:
res
+=
1
else
:
with
nogil
(
False
):
res
=
len
(
x
)
return
res
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment