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):
def go(g, f, *argv, **kw):
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):
g._wg.wait()
......
......@@ -24,6 +24,7 @@ from golang import go, chan, _PanicError
from golang import sync, context
import time, threading
from pytest import raises
from six.moves import range as xrange
def test_once():
once = sync.Once()
......@@ -162,3 +163,32 @@ def test_workgroup():
cancel() # parent cancel - must be propagated into workgroup
wg.wait()
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