Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gevent
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
Kirill Smelkov
gevent
Commits
b3537301
Commit
b3537301
authored
Feb 24, 2018
by
Jason Madden
Committed by
GitHub
Feb 24, 2018
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1120 from gevent/faster-stack
Speed up Greenlet creation on CPython
parents
b84e1bd9
8d9ed09b
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
207 additions
and
136 deletions
+207
-136
CHANGES.rst
CHANGES.rst
+6
-4
src/gevent/_greenlet.pxd
src/gevent/_greenlet.pxd
+30
-8
src/gevent/greenlet.py
src/gevent/greenlet.py
+35
-25
src/greentest/bench_spawn.py
src/greentest/bench_spawn.py
+136
-99
No files found.
CHANGES.rst
View file @
b3537301
...
...
@@ -87,13 +87,15 @@
- Greenlet objects now keep track of their spawning parent greenlet
and the code location that spawned them, in addition to maintaining
a "spawn tree local" mapping. Based on a proposal from PayPal and
comments by Mahmoud Hashemi and Kurt Rose. See :issue:`755` and
:pr:`1115`. As always, feedback is appreciated.
a "spawn tree local" mapping. This adds some runtime overhead in
relative terms, but absolute numbers are still relatively small.
Based on a proposal from PayPal and comments by Mahmoud Hashemi and
Kurt Rose. See :issue:`755` and :pr:`1115`. As always, feedback is
appreciated.
- The :mod:`gevent.greenlet` module is now compiled with Cython to
offset any performance decrease due to :issue:`755`. Please open
issues for any compatibility concerns. See :pr:`1115`.
issues for any compatibility concerns. See :pr:`1115`
and :pr:`1120`
.
- Greenlet objects now have a ``minimal_ident`` property. It functions
similarly to ``Thread.ident`` or ``id`` by uniquely identifying the
...
...
src/gevent/_greenlet.pxd
View file @
b3537301
...
...
@@ -4,6 +4,8 @@ cimport cython
from
gevent.__ident
cimport
IdentRegistry
cdef
bint
_greenlet_imported
cdef
bint
_PYPY
cdef
sys_getframe
cdef
sys_exc_info
cdef
extern
from
"greenlet/greenlet.h"
:
...
...
@@ -25,6 +27,23 @@ cdef inline void greenlet_init():
PyGreenlet_Import
()
_greenlet_imported
=
True
cdef
extern
from
"Python.h"
:
ctypedef
class
types
.
CodeType
[
object
PyCodeObject
]:
pass
cdef
extern
from
"frameobject.h"
:
ctypedef
class
types
.
FrameType
[
object
PyFrameObject
]:
cdef
CodeType
f_code
cdef
int
f_lineno
# We can't declare this in the object, because it's
# allowed to be NULL, and Cython can't handle that.
# We have to go through the python machinery to get a
# proper None instead.
# cdef FrameType f_back
cdef
void
_init
()
cdef
class
SpawnedLink
:
...
...
@@ -42,18 +61,18 @@ cdef class FailureSpawnedLink(SpawnedLink):
@
cython
.
final
@
cython
.
internal
cdef
class
_Frame
:
cdef
readonly
object
f_code
cdef
readonly
CodeType
f_code
cdef
readonly
int
f_lineno
cdef
public
_Frame
f_back
cdef
readonly
_Frame
f_back
@
cython
.
final
@
cython
.
locals
(
previous
=
_Frame
,
first
=
_Frame
,
next_frame
=
_Frame
)
cdef
_Frame
_extract_stack
(
int
limit
,
_Frame
f_back
)
@
cython
.
locals
(
frames
=
list
,
frame
=
FrameType
)
cdef
inline
list
_extract_stack
(
int
limit
)
@
cython
.
final
@
cython
.
locals
(
previous
=
_Frame
,
frame
=
tuple
,
f
=
_Frame
)
cdef
_Frame
_Frame_from_list
(
list
frames
)
cdef
class
Greenlet
(
greenlet
):
...
...
@@ -61,7 +80,10 @@ cdef class Greenlet(greenlet):
cdef
readonly
args
cdef
readonly
object
spawning_greenlet
cdef
public
dict
spawn_tree_locals
cdef
readonly
_Frame
spawning_stack
# This is accessed with getattr() dynamically so it
# must be visible to Python
cdef
readonly
list
_spawning_stack_frames
cdef
list
_links
cdef
tuple
_exc_info
...
...
src/gevent/greenlet.py
View file @
b3537301
...
...
@@ -2,13 +2,15 @@
# cython: auto_pickle=False,embedsignature=True,always_allow_keywords=False
from
__future__
import
absolute_import
,
print_function
,
division
import
sys
from
sys
import
_getframe
as
sys_getframe
from
sys
import
exc_info
as
sys_exc_info
from
weakref
import
ref
as
wref
from
greenlet
import
greenlet
from
gevent._compat
import
reraise
from
gevent._compat
import
PYPY
as
_PYPY
from
gevent._tblib
import
dump_traceback
from
gevent._tblib
import
load_traceback
from
gevent.hub
import
GreenletExit
...
...
@@ -18,8 +20,7 @@ from gevent.hub import get_hub
from
gevent.hub
import
iwait
from
gevent.hub
import
wait
from
gevent.timeout
import
Timeout
_PYPY
=
hasattr
(
sys
,
'pypy_version_info'
)
from
gevent._util
import
Lazy
__all__
=
[
...
...
@@ -108,26 +109,28 @@ class _Frame(object):
self
.
f_lineno
=
f_lineno
self
.
f_back
=
None
f_globals
=
property
(
lambda
_self
:
None
)
@
property
def
f_globals
(
self
):
return
None
def
_
extract_stack
(
limit
,
f_back
):
def
_
Frame_from_list
(
frames
):
previous
=
None
frame
=
sys
.
_getframe
()
first
=
None
for
frame
in
reversed
(
frames
):
f
=
_Frame
(
*
frame
)
f
.
f_back
=
previous
previous
=
f
return
previous
first
=
previous
=
_Frame
(
frame
.
f_code
,
frame
.
f_lineno
)
limit
-=
1
frame
=
frame
.
f_back
def
_extract_stack
(
limit
):
frame
=
sys_getframe
()
frame
s
=
[]
while
limit
and
frame
is
not
None
:
limit
-=
1
next_frame
=
_Frame
(
frame
.
f_code
,
frame
.
f_lineno
)
previous
.
f_back
=
next_frame
previous
=
next_frame
frames
.
append
((
frame
.
f_code
,
frame
.
f_lineno
))
frame
=
frame
.
f_back
previous
.
f_back
=
f_back
return
first
return
frames
_greenlet__init__
=
greenlet
.
__init__
...
...
@@ -274,9 +277,16 @@ class Greenlet(greenlet):
# Its children get separate locals.
spawner
.
spawn_tree_locals
=
self
.
spawn_tree_locals
self
.
_spawning_stack_frames
=
_extract_stack
(
self
.
spawning_stack_limit
)
self
.
_spawning_stack_frames
.
extend
(
getattr
(
spawner
,
'_spawning_stack_frames'
,
[]))
self
.
spawning_stack
=
_extract_stack
(
self
.
spawning_stack_limit
,
getattr
(
spawner
,
'spawning_stack'
,
None
))
@
Lazy
def
spawning_stack
(
self
):
# Store this in the __dict__. We don't use it from the C
# code. It's tempting to discard _spawning_stack_frames
# after this, but child greenlets may still be created
# that need it.
return
_Frame_from_list
(
self
.
_spawning_stack_frames
)
def
_get_minimal_ident
(
self
):
reg
=
self
.
parent
.
ident_registry
...
...
@@ -476,9 +486,9 @@ class Greenlet(greenlet):
.. versionadded:: 1.1
"""
e
xc_info
=
self
.
_exc_info
if
e
xc_info
is
not
None
and
exc_info
[
0
]
is
not
None
:
return
(
e
xc_info
[
0
],
exc_info
[
1
],
load_traceback
(
exc_info
[
2
]))
e
i
=
self
.
_exc_info
if
e
i
is
not
None
and
ei
[
0
]
is
not
None
:
return
(
e
i
[
0
],
ei
[
1
],
load_traceback
(
ei
[
2
]))
def
throw
(
self
,
*
args
):
"""Immediately switch into the greenlet and raise an exception in it.
...
...
@@ -708,7 +718,7 @@ class Greenlet(greenlet):
try
:
result
=
self
.
_run
(
*
self
.
args
,
**
self
.
kwargs
)
except
:
# pylint:disable=bare-except
self
.
_report_error
(
sys
.
exc_info
())
self
.
_report_error
(
sys
_
exc_info
())
return
self
.
_report_result
(
result
)
finally
:
...
...
@@ -801,7 +811,7 @@ class Greenlet(greenlet):
try
:
link
(
self
)
except
:
# pylint:disable=bare-except
self
.
parent
.
handle_error
((
link
,
self
),
*
sys
.
exc_info
())
self
.
parent
.
handle_error
((
link
,
self
),
*
sys
_
exc_info
())
class
_dummy_event
(
object
):
...
...
@@ -828,7 +838,7 @@ def _kill(glet, exception, waiter):
glet
.
throw
(
exception
)
except
:
# pylint:disable=bare-except
# XXX do we need this here?
glet
.
parent
.
handle_error
(
glet
,
*
sys
.
exc_info
())
glet
.
parent
.
handle_error
(
glet
,
*
sys
_
exc_info
())
if
waiter
is
not
None
:
waiter
.
switch
()
...
...
@@ -863,7 +873,7 @@ def _killall3(greenlets, exception, waiter):
try
:
g
.
throw
(
exception
)
except
:
# pylint:disable=bare-except
g
.
parent
.
handle_error
(
g
,
*
sys
.
exc_info
())
g
.
parent
.
handle_error
(
g
,
*
sys
_
exc_info
())
if
not
g
.
dead
:
diehards
.
append
(
g
)
waiter
.
switch
(
diehards
)
...
...
@@ -875,7 +885,7 @@ def _killall(greenlets, exception):
try
:
g
.
throw
(
exception
)
except
:
# pylint:disable=bare-except
g
.
parent
.
handle_error
(
g
,
*
sys
.
exc_info
())
g
.
parent
.
handle_error
(
g
,
*
sys
_
exc_info
())
def
killall
(
greenlets
,
exception
=
GreenletExit
,
block
=
True
,
timeout
=
None
):
...
...
src/greentest/bench_spawn.py
View file @
b3537301
"""Benchmarking spawn() performance.
"""
from
__future__
import
print_function
,
absolute_import
,
division
import
sys
import
os
import
time
import
perf
try
:
xrange
except
NameError
:
xrange
=
range
if
hasattr
(
time
,
"perf_counter"
):
curtime
=
time
.
perf_counter
# 3.3
elif
sys
.
platform
.
startswith
(
'win'
):
curtime
=
time
.
clock
else
:
curtime
=
time
.
time
N
=
10000
0
N
=
10000
counter
=
0
def
incr
(
sleep
,
**
_kwargs
):
global
counter
counter
+=
1
sleep
(
0
)
def
noop
(
_p
):
pass
class
Options
(
object
):
# TODO: Add back an argument for that
eventlet_hub
=
None
loops
=
None
def
__init__
(
self
,
sleep
,
join
,
**
kwargs
):
self
.
kwargs
=
kwargs
self
.
sleep
=
sleep
self
.
join
=
join
class
Times
(
object
):
def
__init__
(
self
,
spawn_duration
,
sleep_duration
=-
1
,
join_duration
=-
1
):
self
.
spawn_duration
=
spawn_duration
self
.
sleep_duration
=
sleep_duration
self
.
join_duration
=
join_duration
def
_report
(
name
,
delta
):
print
(
'%8s: %3.2f microseconds per greenlet'
%
(
name
,
delta
*
1000000.0
/
N
))
def
test
(
spawn
,
sleep
,
kwargs
):
start
=
curtime
()
def
_test
(
spawn
,
sleep
,
options
):
global
counter
counter
=
0
before_spawn
=
perf
.
perf_counter
()
for
_
in
xrange
(
N
):
spawn
(
incr
,
sleep
,
**
kwargs
)
_report
(
'spawning'
,
curtime
()
-
start
)
assert
counter
==
0
,
counter
start
=
curtime
()
sleep
(
0
)
_report
(
'sleep(0)'
,
curtime
()
-
start
)
assert
counter
==
N
,
(
counter
,
N
)
spawn
(
incr
,
sleep
,
**
options
.
kwargs
)
before_sleep
=
perf
.
perf_counter
()
if
options
.
sleep
:
assert
counter
==
0
,
counter
sleep
(
0
)
after_sleep
=
perf
.
perf_counter
()
assert
counter
==
N
,
(
counter
,
N
)
else
:
after_sleep
=
before_sleep
if
options
.
join
:
before_join
=
perf
.
perf_counter
()
options
.
join
()
after_join
=
perf
.
perf_counter
()
join_duration
=
after_join
-
before_join
else
:
join_duration
=
-
1
return
Times
(
before_sleep
-
before_spawn
,
after_sleep
-
before_sleep
,
join_duration
)
def
test
(
spawn
,
sleep
,
options
):
all_times
=
[
_test
(
spawn
,
sleep
,
options
)
for
_
in
xrange
(
options
.
loops
)]
spawn_duration
=
sum
(
x
.
spawn_duration
for
x
in
all_times
)
sleep_duration
=
sum
(
x
.
sleep_duration
for
x
in
all_times
)
join_duration
=
sum
(
x
.
sleep_duration
for
x
in
all_times
if
x
!=
-
1
)
return
Times
(
spawn_duration
,
sleep_duration
,
join_duration
)
def
bench_none
(
options
):
kwargs
=
options
.
kwargs
start
=
curtime
()
for
_
in
xrange
(
N
):
incr
(
noop
,
**
kwargs
)
assert
counter
==
N
,
(
counter
,
N
)
_report
(
'noop'
,
curtime
()
-
start
)
options
.
sleep
=
False
def
spawn
(
f
,
sleep
,
**
kwargs
):
return
f
(
sleep
,
**
kwargs
)
from
time
import
sleep
return
test
(
spawn
,
sleep
,
options
)
def
bench_gevent
(
options
):
import
gevent
print
(
'using gevent from %s'
%
gevent
.
__file__
)
from
gevent
import
spawn
,
sleep
test
(
spawn
,
sleep
,
options
.
kwarg
s
)
return
test
(
spawn
,
sleep
,
option
s
)
def
bench_geventraw
(
options
):
import
gevent
print
(
'using gevent from %s'
%
gevent
.
__file__
)
from
gevent
import
sleep
,
spawn_raw
test
(
spawn_raw
,
sleep
,
options
.
kwarg
s
)
return
test
(
spawn_raw
,
sleep
,
option
s
)
def
bench_geventpool
(
options
):
import
gevent
print
(
'using gevent from %s'
%
gevent
.
__file__
)
from
gevent
import
sleep
from
gevent.pool
import
Pool
p
=
Pool
()
test
(
p
.
spawn
,
sleep
,
options
.
kwargs
)
start
=
curtime
()
p
.
join
()
_report
(
'joining'
,
curtime
()
-
start
)
if
options
.
join
:
options
.
join
=
p
.
join
times
=
test
(
p
.
spawn
,
sleep
,
options
)
return
times
def
bench_eventlet
(
options
):
try
:
import
eventlet
except
ImportError
:
if
options
.
ignore_import_errors
:
return
raise
print
(
'using eventlet from %s'
%
eventlet
.
__file__
)
from
eventlet
import
spawn
,
sleep
from
eventlet.hubs
import
use_hub
if
options
.
eventlet_hub
is
not
None
:
use_hub
(
options
.
eventlet_hub
)
test
(
spawn
,
sleep
,
options
.
kwargs
)
def
bench_all
():
from
time
import
sleep
error
=
0
names
=
sorted
(
all
())
for
func
in
names
:
cmd
=
'%s %s %s --ignore-import-errors'
%
(
sys
.
executable
,
__file__
,
func
)
print
(
cmd
)
sys
.
stdout
.
flush
()
sleep
(
0.01
)
if
os
.
system
(
cmd
):
error
=
1
print
(
'%s failed'
%
cmd
)
print
(
''
)
for
func
in
names
:
cmd
=
'%s %s --with-kwargs %s --ignore-import-errors'
%
(
sys
.
executable
,
__file__
,
func
)
print
(
cmd
)
sys
.
stdout
.
flush
()
if
os
.
system
(
cmd
):
error
=
1
print
(
'%s failed'
%
cmd
)
print
(
''
)
if
error
:
sys
.
exit
(
1
)
return
test
(
spawn
,
sleep
,
options
)
def
all
():
result
=
[
x
for
x
in
globals
()
if
x
.
startswith
(
'bench_'
)
and
x
!=
'bench_all'
]
try
:
result
.
sort
(
key
=
lambda
x
:
globals
()[
x
].
func_code
.
co_firstlineno
)
except
AttributeError
:
result
.
sort
(
key
=
lambda
x
:
globals
()[
x
].
__code__
.
co_firstlineno
)
result
.
sort
()
result
=
[
x
.
replace
(
'bench_'
,
''
)
for
x
in
result
]
return
result
def
all_functions
():
return
[
globals
()[
'bench_%s'
%
x
]
for
x
in
all
()]
def
main
():
import
argparse
parser
=
argparse
.
ArgumentParser
()
parser
.
add_argument
(
'--with-kwargs'
,
default
=
False
,
action
=
'store_true'
)
parser
.
add_argument
(
'--eventlet-hub'
)
parser
.
add_argument
(
'--ignore-import-errors'
,
action
=
'store_true'
)
parser
.
add_argument
(
'benchmark'
,
choices
=
all
()
+
[
'all'
])
options
=
parser
.
parse_args
()
if
options
.
with_kwargs
:
options
.
kwargs
=
{
'foo'
:
1
,
'bar'
:
'hello'
}
else
:
options
.
kwargs
=
{}
if
options
.
benchmark
==
'all'
:
bench_all
()
def
worker_cmd
(
cmd
,
args
):
cmd
.
extend
(
args
.
benchmark
)
runner
=
perf
.
Runner
(
add_cmdline_args
=
worker_cmd
)
runner
.
argparser
.
add_argument
(
'benchmark'
,
nargs
=
'*'
,
default
=
'all'
,
choices
=
all
()
+
[
'all'
])
def
spawn_time
(
loops
,
func
,
options
):
options
.
loops
=
loops
times
=
func
(
options
)
return
times
.
spawn_duration
def
sleep_time
(
loops
,
func
,
options
):
options
.
loops
=
loops
times
=
func
(
options
)
return
times
.
sleep_duration
def
join_time
(
loops
,
func
,
options
):
options
.
loops
=
loops
times
=
func
(
options
)
return
times
.
join_duration
args
=
runner
.
parse_args
()
if
'all'
in
args
.
benchmark
or
args
.
benchmark
==
'all'
:
args
.
benchmark
=
[
'all'
]
names
=
all
()
else
:
function
=
globals
()[
'bench_'
+
options
.
benchmark
]
function
(
options
)
names
=
args
.
benchmark
names
=
sorted
(
set
(
names
))
for
name
in
names
:
runner
.
bench_time_func
(
name
+
' spawn'
,
spawn_time
,
globals
()[
'bench_'
+
name
],
Options
(
False
,
False
),
inner_loops
=
N
)
if
name
!=
'none'
:
runner
.
bench_time_func
(
name
+
' sleep'
,
sleep_time
,
globals
()[
'bench_'
+
name
],
Options
(
True
,
False
),
inner_loops
=
N
)
if
'geventpool'
in
names
:
runner
.
bench_time_func
(
'geventpool join'
,
join_time
,
bench_geventpool
,
Options
(
True
,
True
),
inner_loops
=
N
)
for
name
in
names
:
runner
.
bench_time_func
(
name
+
' spawn kwarg'
,
spawn_time
,
globals
()[
'bench_'
+
name
],
Options
(
False
,
False
,
foo
=
1
,
bar
=
'hello'
),
inner_loops
=
N
)
if
__name__
==
'__main__'
:
main
()
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