Commit 1bcb8297 authored by Kirill Smelkov's avatar Kirill Smelkov

golang.pyx: Switch pychan from `class` to `cdef class`

We will need to add C-level attributes to pychan and this requires it to
become cdef class. The class is exported because at least
_golang_test.pyx will also need to have access to those attributes.

If we just do `class pychan` -> `cdef class pychan` e.g. the following
starts to break:

    1.venv/local/lib/python2.7/site-packages/py/_path/local.py:701: in pyimport
        __import__(modname)
    golang/__init__.py:174: in <module>
        from ._golang import    \
    golang/_golang.pyx:455: in init golang._golang
        _pychan_send  = _pychan_send.__func__
    E   AttributeError: 'method_descriptor' object has no attribute '__func__'

and

    golang/_golang.pyx:513: in golang._golang.pyselect
        if im_class(recv) is not pychan:
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

    f = <built-in method recv of golang._golang.pychan object at 0x7f7055e57cc8>

        def im_class(f):
    >       return f.im_class
    E       AttributeError: 'builtin_function_or_method' object has no attribute 'im_class'

    golang/_pycompat.py:28: AttributeError

This is probably because for `cdef class` methods Cython does not
emulate full method bindings the same way as Python does.  Anyway we can
check which method is passed to pyselect by chanop.__name__ or by
inspecting PyCFunction directly. And not having method binding wrapper
should only remove a bit of overhead.

So we are ok with reworking send/recv chanop detection, and since
this way im_class provided by golang._pycompat becomes unused, it is
also removed.

The timings are probably within noise:

 (on i7@2.6GHz)

thread runtime:

    name             old time/op  new time/op  delta
    go               21.7µs ± 1%  20.0µs ± 1%  -7.60%  (p=0.000 n=10+10)
    chan             9.91µs ± 4%  9.37µs ± 4%  -5.39%  (p=0.000 n=10+10)
    select           19.2µs ± 4%  20.2µs ± 4%  +5.62%  (p=0.001 n=9+8)
    def              58.0ns ± 0%  58.0ns ± 0%    ~     (all equal)
    func_def         44.4µs ± 0%  43.8µs ± 1%  -1.22%  (p=0.000 n=10+10)
    call             63.0ns ± 0%  62.4ns ± 1%  -0.95%  (p=0.011 n=10+10)
    func_call        1.05µs ± 1%  1.06µs ± 1%    ~     (p=0.059 n=10+10)
    try_finally       135ns ± 0%   136ns ± 0%  +0.74%  (p=0.000 n=10+9)
    defer            2.36µs ± 1%  2.28µs ± 1%  -3.59%  (p=0.000 n=10+10)
    workgroup_empty  49.0µs ± 1%  48.2µs ± 1%  -1.63%  (p=0.000 n=10+9)
    workgroup_raise  62.6µs ± 1%  58.9µs ± 1%  -5.96%  (p=0.000 n=10+10)

gevent runtime:

    name             old time/op  new time/op  delta
    go               21.7µs ± 1%  20.5µs ± 1%  -5.33%  (p=0.000 n=10+9)
    chan             9.91µs ± 4%  9.72µs ± 5%    ~     (p=0.190 n=10+10)
    select           19.2µs ± 4%  19.5µs ±14%    ~     (p=0.968 n=9+10)
    def              58.0ns ± 0%  58.0ns ± 0%    ~     (all equal)
    func_def         44.4µs ± 0%  45.4µs ± 1%  +2.23%  (p=0.000 n=10+10)
    call             63.0ns ± 0%  64.0ns ± 0%  +1.59%  (p=0.000 n=10+10)
    func_call        1.05µs ± 1%  1.06µs ± 0%  +0.65%  (p=0.002 n=10+10)
    try_finally       135ns ± 0%   137ns ± 0%  +1.48%  (p=0.000 n=10+10)
    defer            2.36µs ± 1%  2.38µs ± 1%  +0.72%  (p=0.006 n=10+10)
    workgroup_empty  49.0µs ± 1%  48.2µs ± 1%  -1.65%  (p=0.000 n=10+10)
    workgroup_raise  62.6µs ± 1%  60.3µs ± 1%  -3.69%  (p=0.000 n=10+10)
parent a0714b8e
......@@ -63,3 +63,9 @@ cdef extern from "golang/libgolang.h" namespace "golang" nogil:
cdef void topyexc() except *
cpdef pypanic(arg)
from cython cimport final
@final
cdef class pychan:
cdef dict __dict__
# -*- coding: utf-8 -*-
# cython: language_level=2
# cython: binding=False
# distutils: language = c++
# distutils: depends = libgolang.h
#
......@@ -35,9 +36,7 @@ from cpython cimport Py_INCREF, Py_DECREF, PY_MAJOR_VERSION
from cython cimport final
import sys
import six
import threading, collections, random
from golang._pycompat import im_class
# ---- panic ----
......@@ -253,7 +252,8 @@ def _dequeWaiter(queue):
# pychan is Python channel with Go semantic.
class pychan(object):
@final
cdef class pychan:
# ._cap channel capacity
# ._mu lock
# ._dataq deque *: data buffer
......@@ -446,16 +446,6 @@ pynilchan = pychan(None) # TODO -> <chan*>(NULL) after move to Cython
# pydefault represents default case for pyselect.
pydefault = object()
# unbound pychan.{send,recv,recv_}
_pychan_send = pychan.send
_pychan_recv = pychan.recv
_pychan_recv_ = pychan.recv_
if six.PY2:
# on py3 class.func gets the func; on py2 - unbound_method(func)
_pychan_send = _pychan_send.__func__
_pychan_recv = _pychan_recv.__func__
_pychan_recv_ = _pychan_recv_.__func__
# pyselect executes one ready send or receive channel case.
#
# if no case is ready and default case was provided, select chooses default.
......@@ -502,9 +492,9 @@ def pyselect(*casev):
# send
elif isinstance(case, tuple):
send, tx = case
if im_class(send) is not pychan:
pypanic("pyselect: send on non-chan: %r" % (im_class(send),))
if send.__func__ is not _pychan_send:
if send.__self__.__class__ is not pychan:
pypanic("pyselect: send on non-chan: %r" % (send.__self__.__class__,))
if send.__name__ != "send": # XXX better check PyCFunction directly
pypanic("pyselect: send expected: %r" % (send,))
ch = send.__self__
......@@ -521,11 +511,11 @@ def pyselect(*casev):
# recv
else:
recv = case
if im_class(recv) is not pychan:
pypanic("pyselect: recv on non-chan: %r" % (im_class(recv),))
if recv.__func__ is _pychan_recv:
if recv.__self__.__class__ is not pychan:
pypanic("pyselect: recv on non-chan: %r" % (recv.__self__.__class__,))
if recv.__name__ == "recv": # XXX better check PyCFunction directly
commaok = False
elif recv.__func__ is _pychan_recv_:
elif recv.__name__ == "recv_": # XXX better check PyCFunction directly
commaok = True
else:
pypanic("pyselect: recv expected: %r" % (recv,))
......
......@@ -30,8 +30,6 @@ from golang cimport go, panic, pypanic, topyexc
from golang import chan, nilchan
from golang import time
from golang._golang import _pychan_recv, _pychan_send
from golang._pycompat import im_class
# pylen_{recv,send}q returns len(ch._{recv,send}q)
def pylen_recvq(ch):
......@@ -47,13 +45,13 @@ def pylen_sendq(ch):
#
# For example `pywaitBlocked(ch.send)` waits till sender blocks waiting on ch.
def pywaitBlocked(chanop):
if im_class(chanop) is not chan:
pypanic("wait blocked: %r is method of a non-chan: %r" % (chanop, im_class(chanop)))
if chanop.__self__.__class__ is not chan:
pypanic("wait blocked: %r is method of a non-chan: %r" % (chanop, chanop.__self__.__class__))
ch = chanop.__self__
recv = send = False
if chanop.__func__ is _pychan_recv:
if chanop.__name__ == "recv": # XXX better check PyCFunction directly
recv = True
elif chanop.__func__ is _pychan_send:
elif chanop.__name__ == "send": # XXX better check PyCFunction directly
send = True
else:
pypanic("wait blocked: unexpected chan method: %r" % (chanop,))
......
# -*- coding: utf-8 -*-
# Copyright (C) 2018-2019 Nexedi SA and Contributors.
# Kirill Smelkov <kirr@nexedi.com>
#
# This program is free software: you can Use, Study, Modify and Redistribute
# it under the terms of the GNU General Public License version 3, or (at your
# option) any later version, as published by the Free Software Foundation.
#
# You can also Link and Combine this program with other software covered by
# the terms of any of the Free Software licenses or any of the Open Source
# Initiative approved licenses and Convey the resulting work. Corresponding
# source of such a combination shall include the source code for all other
# software used.
#
# This program is distributed WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# See COPYING file for full licensing terms.
# See https://www.nexedi.com/licensing for rationale and options.
"""compatibility in between py2/py3"""
from __future__ import print_function, absolute_import
from six import PY2
if PY2:
def im_class(f):
return f.im_class
# PY3
else:
def im_class(f):
return f.__self__.__class__
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