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:
William S Fulton 2018-08-17 23:34:29 +01:00
commit c9cac931c7
6 changed files with 144 additions and 32 deletions

View file

@ -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.

View file

@ -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>

View file

@ -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>

View file

@ -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)

View file

@ -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
}

View file

@ -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);
}