Re-implement Python -fastproxy option.
The previous implementation failed with Python 3 and abstract base clases. The new implementation replaces the Python 2 implementation using new.instancemethod with C API PyMethod_New to match the equivalent Python 3 implementation which uses PyInstanceMethod_New. Closes #1310
This commit is contained in:
parent
bb5c46079b
commit
c9cac931c7
6 changed files with 144 additions and 32 deletions
|
|
@ -7,6 +7,16 @@ the issue number to the end of the URL: https://github.com/swig/swig/issues/
|
|||
Version 4.0.0 (in progress)
|
||||
===========================
|
||||
|
||||
2018-08-18: wsfulton
|
||||
[Python] #1310 Re-implement Python -fastproxy option.
|
||||
|
||||
The previous implementation failed with Python 3 and abstract base clases.
|
||||
The new implementation replaces the Python 2 implementation using
|
||||
new.instancemethod with the C API PyMethod_New to match the equivalent Python 3
|
||||
implementation which uses PyInstanceMethod_New.
|
||||
|
||||
The new approach runs slightly faster. See #1310.
|
||||
|
||||
2018-08-12: gmazzamuto
|
||||
[Python] #1283 Update pybuffer.i library to use new-style Python buffer C API.
|
||||
|
||||
|
|
|
|||
|
|
@ -1642,6 +1642,10 @@
|
|||
<li><a href="Python.html#Python_nn42">Adding additional Python code</a>
|
||||
<li><a href="Python.html#Python_nn43">Class extension with %extend</a>
|
||||
<li><a href="Python.html#Python_nn44">Exception handling with %exception</a>
|
||||
<li><a href="Python.html#Python_optimization">Optimization options</a>
|
||||
<ul>
|
||||
<li><a href="Python.html#Python_fastproxy">-fastproxy</a>
|
||||
</ul>
|
||||
</ul>
|
||||
<li><a href="Python.html#Python_nn45">Tips and techniques</a>
|
||||
<ul>
|
||||
|
|
|
|||
|
|
@ -71,6 +71,10 @@
|
|||
<li><a href="#Python_nn42">Adding additional Python code</a>
|
||||
<li><a href="#Python_nn43">Class extension with %extend</a>
|
||||
<li><a href="#Python_nn44">Exception handling with %exception</a>
|
||||
<li><a href="#Python_optimization">Optimization options</a>
|
||||
<ul>
|
||||
<li><a href="#Python_fastproxy">-fastproxy</a>
|
||||
</ul>
|
||||
</ul>
|
||||
<li><a href="#Python_nn45">Tips and techniques</a>
|
||||
<ul>
|
||||
|
|
@ -3797,6 +3801,99 @@ The language-independent <tt>exception.i</tt> library file can also be used
|
|||
to raise exceptions. See the <a href="Library.html#Library">SWIG Library</a> chapter.
|
||||
</p>
|
||||
|
||||
<H3><a name="Python_optimization">38.6.5 Optimization options</a></H3>
|
||||
|
||||
|
||||
<H4><a name="Python_fastproxy">38.6.5.1 -fastproxy</a></H4>
|
||||
|
||||
|
||||
<p>
|
||||
The <tt>-fastproxy</tt> command line option enables faster method calling as the call is made directly into the C/C++ layer rather than going through a method wrapper.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Consider wrapping a C++ class:
|
||||
</p>
|
||||
|
||||
<div class="code">
|
||||
<pre>
|
||||
struct Go {
|
||||
void callme0() {}
|
||||
void callme4(int a, int b, int c, int d) {}
|
||||
void callme8(double a, double b, double c, double d, double e, double f, double g, double i) {}
|
||||
};
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
The default generated proxy class is:
|
||||
</p>
|
||||
|
||||
<div class="targetlang">
|
||||
<pre>
|
||||
class Go(object):
|
||||
def callme0(self):
|
||||
return _example.Go_callme0(self)
|
||||
|
||||
def callme4(self, a, b, c, d):
|
||||
return _example.Go_callme4(self, a, b, c, d)
|
||||
|
||||
def callme8(self, a, b, c, d, e, f, g, i):
|
||||
return _example.Go_callme8(self, a, b, c, d, e, f, g, i)
|
||||
...
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
The generated code when using <tt>-fastproxy</tt> is:
|
||||
</p>
|
||||
|
||||
<div class="targetlang">
|
||||
<pre>
|
||||
%module example
|
||||
class Go(_object):
|
||||
callme0 = _swig_new_instance_method(_example.Go_callme0)
|
||||
callme4 = _swig_new_instance_method(_example.Go_callme4)
|
||||
callme8 = _swig_new_instance_method(_example.Go_callme8)
|
||||
...
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
where <tt>_swig_new_instance_method</tt> adds the method to the proxy class via C API calls.
|
||||
The overhead calling into C/C++ from Python is reduced slightly using <tt>-fastproxy</tt>.
|
||||
Below are some timings in microseconds calling the 3 functions in the example above:
|
||||
</p>
|
||||
|
||||
<table summary="Python fastproxy performance">
|
||||
<tr>
|
||||
<th>Method name</th>
|
||||
<th>Without -proxy</th>
|
||||
<th>With -proxy</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>callme0</td>
|
||||
<td>0.57</td>
|
||||
<td>0.48</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>callme4</td>
|
||||
<td>0.64</td>
|
||||
<td>0.54</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>callme8</td>
|
||||
<td>0.73</td>
|
||||
<td>0.57</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p>
|
||||
Although the <tt>-fastproxy</tt> option results in faster code, the generated proxy code is not as user-friendly
|
||||
as docstring/doxygen comments and functions with default values are not visible in the generated python proxy class.
|
||||
</p>
|
||||
|
||||
|
||||
<H2><a name="Python_nn45">38.7 Tips and techniques</a></H2>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -13,15 +13,20 @@ def check(got, expected, expected_builtin=None, skip=False):
|
|||
def is_new_style_class(cls):
|
||||
return hasattr(cls, "__class__")
|
||||
|
||||
def is_fastproxy(module):
|
||||
return "new_instancemethod" in module
|
||||
def is_fastproxy():
|
||||
fastproxy = True
|
||||
try:
|
||||
from autodoc import _swig_new_instance_method
|
||||
except ImportError:
|
||||
fastproxy = False
|
||||
return fastproxy
|
||||
|
||||
if not is_new_style_class(A):
|
||||
# Missing static methods make this hard to test... skip if -classic is
|
||||
# used!
|
||||
sys.exit(0)
|
||||
|
||||
if is_fastproxy(dir()):
|
||||
if is_fastproxy():
|
||||
# Detect when -fastproxy is specified and skip test as it changes the function names making it
|
||||
# hard to test... skip until the number of options are reduced in SWIG-3.1 and autodoc is improved
|
||||
sys.exit(0)
|
||||
|
|
|
|||
|
|
@ -29,17 +29,14 @@ typedef struct swig_const_info {
|
|||
* Wrapper of PyInstanceMethod_New() used in Python 3
|
||||
* It is exported to the generated module, used for -fastproxy
|
||||
* ----------------------------------------------------------------------------- */
|
||||
#if PY_VERSION_HEX >= 0x03000000
|
||||
SWIGRUNTIME PyObject* SWIG_PyInstanceMethod_New(PyObject *SWIGUNUSEDPARM(self), PyObject *func)
|
||||
{
|
||||
#if PY_VERSION_HEX >= 0x03000000
|
||||
return PyInstanceMethod_New(func);
|
||||
}
|
||||
#else
|
||||
SWIGRUNTIME PyObject* SWIG_PyInstanceMethod_New(PyObject *SWIGUNUSEDPARM(self), PyObject *SWIGUNUSEDPARM(func))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
return PyMethod_New(func, NULL, NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
|
|||
|
|
@ -825,18 +825,8 @@ public:
|
|||
}
|
||||
|
||||
Printv(default_import_code, "\nfrom sys import version_info as _swig_python_version_info\n", NULL);
|
||||
|
||||
if (!builtin && fastproxy) {
|
||||
Printv(default_import_code, "if _swig_python_version_info >= (3, 0, 0):\n", NULL);
|
||||
Printf(default_import_code, tab4 "new_instancemethod = lambda func, inst, cls: %s.SWIG_PyInstanceMethod_New(func)\n", module);
|
||||
Printv(default_import_code, "elif _swig_python_version_info >= (2, 7, 0):\n", NULL);
|
||||
Printv(default_import_code, tab4, "from new import instancemethod as new_instancemethod\n", NULL);
|
||||
Printv(default_import_code, "else:\n", NULL);
|
||||
Printv(default_import_code, tab4, "raise RuntimeError('Python 2.7 or later required')\n", NULL);
|
||||
} else {
|
||||
Printv(default_import_code, "if _swig_python_version_info < (2, 7, 0):\n", NULL);
|
||||
Printv(default_import_code, tab4, "raise RuntimeError('Python 2.7 or later required')\n", NULL);
|
||||
}
|
||||
Printv(default_import_code, "if _swig_python_version_info < (2, 7, 0):\n", NULL);
|
||||
Printv(default_import_code, tab4, "raise RuntimeError('Python 2.7 or later required')\n", NULL);
|
||||
|
||||
/* Import the C-extension module. This should be a relative import,
|
||||
* since the shadow module may also have been imported by a relative
|
||||
|
|
@ -897,6 +887,11 @@ public:
|
|||
defined in this module (try both Python 3 and Python 2 names) */
|
||||
Printv(f_shadow, "try:\n", tab4, "import builtins as __builtin__\n", "except ImportError:\n", tab4, "import __builtin__\n", NULL);
|
||||
|
||||
if (!builtin && fastproxy) {
|
||||
Printf(f_shadow, "\n");
|
||||
Printf(f_shadow, "_swig_new_instance_method = %s.SWIG_PyInstanceMethod_New\n", module);
|
||||
}
|
||||
|
||||
/* if (!modern) */
|
||||
/* always needed, a class can be forced to be no-modern, such as an exception */
|
||||
{
|
||||
|
|
@ -4682,32 +4677,36 @@ public:
|
|||
}
|
||||
|
||||
/* Now emit methods */
|
||||
if (!builtin)
|
||||
if (!builtin) {
|
||||
Printv(f_shadow_file, f_shadow, NIL);
|
||||
|
||||
if (fastproxy) {
|
||||
Printf(f_shadow_file, "\n");
|
||||
List *shadow_list = Getattr(n, "shadow_methods");
|
||||
for (int i = 0; i < Len(shadow_list); ++i) {
|
||||
String *symname = Getitem(shadow_list, i);
|
||||
Printf(f_shadow_file, tab4);
|
||||
Printf(f_shadow_file, "%s = _swig_new_instance_method(%s.%s)\n", symname, module, Swig_name_member(NSPACE_TODO, class_name, symname));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Now the Ptr class */
|
||||
if (classptr && !builtin) {
|
||||
Printv(f_shadow_file, "\nclass ", class_name, "Ptr(", class_name, "):\n", tab4, "def __init__(self, this):\n", NIL);
|
||||
if (!modern) {
|
||||
Printv(f_shadow_file,
|
||||
tab8, "try:\n", tab8, tab4, "self.this.append(this)\n",
|
||||
tab8, "except __builtin__.Exception:\n", tab8, tab4, "self.this = this\n", tab8, "self.this.own(0)\n", tab8, "self.__class__ = ", class_name, "\n\n", NIL);
|
||||
tab8, "except __builtin__.Exception:\n", tab8, tab4, "self.this = this\n", tab8, "self.this.own(0)\n", tab8, "self.__class__ = ", class_name, "\n", NIL);
|
||||
} else {
|
||||
Printv(f_shadow_file,
|
||||
tab8, "try:\n", tab8, tab4, "self.this.append(this)\n",
|
||||
tab8, "except __builtin__.Exception:\n", tab8, tab4, "self.this = this\n", tab8, "self.this.own(0)\n", tab8, "self.__class__ = ", class_name, "\n\n", NIL);
|
||||
tab8, "except __builtin__.Exception:\n", tab8, tab4, "self.this = this\n", tab8, "self.this.own(0)\n", tab8, "self.__class__ = ", class_name, "\n", NIL);
|
||||
}
|
||||
}
|
||||
|
||||
if (!builtin) {
|
||||
if (fastproxy) {
|
||||
List *shadow_list = Getattr(n, "shadow_methods");
|
||||
for (int i = 0; i < Len(shadow_list); ++i) {
|
||||
String *symname = Getitem(shadow_list, i);
|
||||
Printf(f_shadow_file, "%s.%s = new_instancemethod(%s.%s, None, %s)\n", class_name, symname, module, Swig_name_member(NSPACE_TODO, class_name, symname),
|
||||
class_name);
|
||||
}
|
||||
}
|
||||
Printf(f_shadow_file, "\n");
|
||||
Printf(f_shadow_file, "# Register %s in %s:\n", class_name, module);
|
||||
Printf(f_shadow_file, "%s.%s_swigregister(%s)\n", module, class_name, class_name);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue