Commit 94c6160b authored by Kirill Smelkov's avatar Kirill Smelkov

sync.Workgroup: Don't use @func at runtime

It is true for any decorator, that it makes things faster if
def+decorator is used globally instead of at runtime, but for @func it
is especially true since @func, using decorator.decorate, has relatively
high overhead if it is used not only once at program startup, but
instead every time something is run.

In particular moving @func out of WorkGroup.go() make things
considerably faster:

	name             old time/op  new time/op  delta
	workgroup_empty   195µs ± 0%   113µs ± 1%  -41.91%  (p=0.008 n=5+5)
	workgroup_raise   221µs ± 1%   137µs ± 1%  -38.29%  (p=0.008 n=5+5)

See

	https://lab.nexedi.com/kirr/misc/raw/009c4fee/pygolang/prof_workgroup_empty.svg

for bench_workgroup_empty profile, where it is seen that
decorator.decorate was using ~ half of the whole WorkGroup.go() time.
parent 3c55ca59
...@@ -113,23 +113,24 @@ class WorkGroup(object): ...@@ -113,23 +113,24 @@ class WorkGroup(object):
def go(g, f, *argv, **kw): def go(g, f, *argv, **kw):
g._wg.add(1) g._wg.add(1)
go(lambda: g._run(f, *argv, **kw))
@func
def _run(g, f, *argv, **kw):
defer(g._wg.done)
try:
f(g._ctx, *argv, **kw)
except Exception as exc:
with g._mu:
if g._err is None:
# this goroutine is the first failed task
g._err = exc
if six.PY2:
# py3 has __traceback__ automatically
exc.__traceback__ = sys.exc_info()[2]
g._cancel()
@func
def _():
defer(g._wg.done)
try:
f(g._ctx, *argv, **kw)
except Exception as exc:
with g._mu:
if g._err is None:
# this goroutine is the first failed task
g._err = exc
if six.PY2:
# py3 has __traceback__ automatically
exc.__traceback__ = sys.exc_info()[2]
g._cancel()
go(_)
def wait(g): def wait(g):
g._wg.wait() g._wg.wait()
......
...@@ -24,6 +24,7 @@ from golang import go, chan, _PanicError ...@@ -24,6 +24,7 @@ from golang import go, chan, _PanicError
from golang import sync, context from golang import sync, context
import time, threading import time, threading
from pytest import raises from pytest import raises
from six.moves import range as xrange
def test_once(): def test_once():
once = sync.Once() once = sync.Once()
...@@ -162,3 +163,32 @@ def test_workgroup(): ...@@ -162,3 +163,32 @@ def test_workgroup():
cancel() # parent cancel - must be propagated into workgroup cancel() # parent cancel - must be propagated into workgroup
wg.wait() wg.wait()
assert l == [1, 2] assert l == [1, 2]
# create/wait workgroup with 1 empty worker.
def bench_workgroup_empty(b):
bg = context.background()
def _(ctx):
return
for i in xrange(b.N):
wg = sync.WorkGroup(bg)
wg.go(_)
wg.wait()
# create/wait workgroup with 1 worker that raises.
def bench_workgroup_raise(b):
bg = context.background()
def _(ctx):
raise RuntimeError('aaa')
for i in xrange(b.N):
wg = sync.WorkGroup(bg)
wg.go(_)
try:
wg.wait()
except RuntimeError:
pass
else:
# NOTE not using `with raises` since it affects benchmark timing
assert False, "did not raise"
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment