Commit 2114a560 authored by Kirill Smelkov's avatar Kirill Smelkov

golang: Add benchmarks for nogil go and channels

on i7@2.6GHz it looks like:

thread runtime:

    name              time/op
    pyx_select_nogil  2.70µs ±13%
    pyx_go_nogil      15.9µs ± 1%
    pyx_chan_nogil    2.79µs ± 2%
    go                17.6µs ± 0%
    chan              3.05µs ± 4%
    select            3.62µs ± 4%

gevent runtime (gevent-1.4.0):

    name              time/op
    pyx_select_nogil  9.39µs ± 1%
    pyx_go_nogil      15.1µs ± 2%
    pyx_chan_nogil    7.10µs ± 1%
    go                16.6µs ± 1%
    chan              7.47µs ± 1%
    select            10.7µs ± 0%
parent 874371e2
...@@ -342,3 +342,77 @@ cdef nogil: ...@@ -342,3 +342,77 @@ cdef nogil:
pych.chan_double().send(obj) pych.chan_double().send(obj)
void _pychan_double_close(pychan pych) except +topyexc: void _pychan_double_close(pychan pych) except +topyexc:
pych.chan_double().close() pych.chan_double().close()
# ---- benchmarks ----
# bench_go_nogil mirrors golang_test.py:bench_go
def bench_go_nogil(b):
cdef int N = b.N
with nogil:
_bench_go_nogil(N)
cdef void _bench_go_nogil(int N) nogil except +topyexc:
cdef chan[int] done = makechan[int]()
for i in range(N):
go(_bench_go_nogil__func1, done)
done.recv()
cdef void _bench_go_nogil__func1(chan[int] done) nogil:
done.send(1)
# bench_chan_nogil mirrors golang_test.py:bench_chan
def bench_chan_nogil(b):
cdef int N = b.N
with nogil:
_bench_chan_nogil(N)
cdef void _bench_chan_nogil(int N) nogil except +topyexc:
cdef chan[int] ch = makechan[int]()
cdef chan[structZ] done = makechan[structZ]()
go(_bench_chan_nogil__func1, ch, done)
for i in range(N):
ch.send(1)
ch.close()
done.recv()
cdef void _bench_chan_nogil__func1(chan[int] ch, chan[structZ] done) nogil:
while 1:
_, ok = recv_(ch)
if not ok:
done.close()
return
# bench_select_nogil mirrors golang_test.py:bench_select
def bench_select_nogil(b):
cdef int N = b.N
with nogil:
_bench_select_nogil(N)
cdef void _bench_select_nogil(int N) nogil except +topyexc:
cdef chan[int] ch1 = makechan[int]()
cdef chan[int] ch2 = makechan[int]()
cdef chan[structZ] done = makechan[structZ]()
go(_bench_select_nogil__func1, ch1, ch2, done)
for i in range(N):
if i&1: ch1.send(1)
else: ch2.send(1)
ch1.close()
done.recv()
cdef void _bench_select_nogil__func1(chan[int] ch1, chan[int] ch2, chan[structZ] done) nogil:
cdef int i
cdef cbool ok
while 1:
_ = select([
ch1.recvs(&i, &ok), # 0
ch2.recvs(&i, &ok), # 1
])
if _ == 0:
if not ok:
done.close()
return
...@@ -32,23 +32,34 @@ from subprocess import Popen, PIPE ...@@ -32,23 +32,34 @@ from subprocess import Popen, PIPE
import six import six
from six.moves import range as xrange from six.moves import range as xrange
import gc, weakref, warnings import gc, weakref, warnings
import re
from golang import _golang_test from golang import _golang_test
from golang._golang_test import pywaitBlocked as waitBlocked, pylen_recvq as len_recvq, \ from golang._golang_test import pywaitBlocked as waitBlocked, pylen_recvq as len_recvq, \
pylen_sendq as len_sendq, pypanicWhenBlocked as panicWhenBlocked pylen_sendq as len_sendq, pypanicWhenBlocked as panicWhenBlocked
# pyx/c/c++ tests -> test_pyx_* in caller's globals. # pyx/c/c++ tests/benchmarks -> {test,bench}_pyx_* in caller's globals.
def import_pyx_tests(modpath): def import_pyx_tests(modpath):
mod = importlib.import_module(modpath) mod = importlib.import_module(modpath)
callf = inspect.currentframe().f_back # caller's frame callf = inspect.currentframe().f_back # caller's frame
callg = callf.f_globals # caller's globals callg = callf.f_globals # caller's globals
tbre = re.compile("(test|bench)_(.+)")
for f in dir(mod): for f in dir(mod):
if f.startswith('test_'): m = tbre.match(f)
gf = 'test_pyx_' + f[len('test_'):] # test_chan_nogil -> test_pyx_chan_nogil if m is not None:
kind, name = m.group(1), m.group(2)
gf = kind + "_pyx_" + name # test_chan_nogil -> test_pyx_chan_nogil
# define a python function with gf name (if we use f directly pytest # define a python function with gf name (if we use f directly pytest
# will say "cannot collect 'test_pyx_chan_nogil' because it is not a function") # will say "cannot collect 'test_pyx_chan_nogil' because it is not a function")
def _(func=getattr(mod, f)): if kind == "test":
func() def _(func=getattr(mod, f)):
func()
elif kind == "bench":
def _(b, func=getattr(mod, f)):
func(b)
else:
panic("unreachable")
_.__name__ = gf _.__name__ = gf
callg[gf] = _ callg[gf] = _
...@@ -61,6 +72,7 @@ def test_go_leaked(): ...@@ -61,6 +72,7 @@ def test_go_leaked():
pyrun([dirname(__file__) + "/testprog/golang_test_goleaked.py"]) pyrun([dirname(__file__) + "/testprog/golang_test_goleaked.py"])
# benchmark go+join a thread/coroutine. # benchmark go+join a thread/coroutine.
# pyx/nogil mirror is in _golang_test.pyx
def bench_go(b): def bench_go(b):
done = chan() done = chan()
def _(): def _():
...@@ -317,6 +329,7 @@ def test_chan_sendrecv_2way(): ...@@ -317,6 +329,7 @@ def test_chan_sendrecv_2way():
# benchmark sync chan send/recv. # benchmark sync chan send/recv.
# pyx/nogil mirror is in _golang_test.pyx
def bench_chan(b): def bench_chan(b):
ch = chan() ch = chan()
done = chan() done = chan()
...@@ -698,6 +711,7 @@ def test_select_refleak(): ...@@ -698,6 +711,7 @@ def test_select_refleak():
# benchmark sync chan send vs recv on select side. # benchmark sync chan send vs recv on select side.
# pyx/nogil mirror is in _golang_test.pyx
def bench_select(b): def bench_select(b):
ch1 = chan() ch1 = chan()
ch2 = chan() ch2 = chan()
......
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