Commit 13952ac9 authored by Stefan Behnel's avatar Stefan Behnel

Speed up calls to non-optimised builtin methods by caching the underlying C function.

parent 859fe33d
......@@ -33,6 +33,9 @@ Features added
* ``dict.pop()`` is optimised.
Original patch by Antoine Pitrou. (Github issue #2047)
* Calls to builtin methods that are not specifically optimised into C-API calls
now use a cache that avoids repeated lookups of the underlying C function.
* Subscripting (item access) is faster in some cases.
* Some ``bytearray`` operations have been optimised similar to ``bytes``.
......
......@@ -512,7 +512,7 @@ class UtilityCode(UtilityCodeBase):
assert 1 <= len(args) <= 3, "CALL_UNBOUND_METHOD() does not support %d call arguments" % len(args)
call = '__Pyx_CallUnboundCMethod%d' % (len(args) - 1,)
utility_code.add("CallUnboundCMethod%d" % (len(args) - 1,))
cname = output.get_cached_unbound_method(type_cname, method_name, len(args))
cname = output.get_cached_unbound_method(type_cname, method_name)
return '%s(&%s, %s)' % (call, cname, ', '.join(args))
impl = re.sub(r'CALL_UNBOUND_METHOD\(([a-zA-Z_]+),\s*"([^"]+)"((?:,\s*[^),]+)+)\)', externalise, impl)
......@@ -1310,8 +1310,8 @@ class GlobalState(object):
prefix = Naming.const_prefix
return "%s%s" % (prefix, name_suffix)
def get_cached_unbound_method(self, type_cname, method_name, args_count):
key = (type_cname, method_name, args_count)
def get_cached_unbound_method(self, type_cname, method_name):
key = (type_cname, method_name)
try:
cname = self.cached_cmethods[key]
except KeyError:
......@@ -1371,7 +1371,7 @@ class GlobalState(object):
decl = self.parts['decls']
init = self.parts['init_globals']
cnames = []
for (type_cname, method_name, _), cname in sorted(self.cached_cmethods.items()):
for (type_cname, method_name), cname in sorted(self.cached_cmethods.items()):
cnames.append(cname)
method_name_cname = self.get_interned_identifier(StringEncoding.EncodedString(method_name)).cname
decl.putln('static __Pyx_CachedCFunction %s = {0, &%s, 0, 0, 0};' % (
......
......@@ -6110,6 +6110,43 @@ class PythonCapiCallNode(SimpleCallNode):
SimpleCallNode.__init__(self, pos, **kwargs)
class CachedBuiltinMethodCallNode(CallNode):
# Python call to a method of a known Python builtin (only created in transforms)
subexprs = ['obj', 'args']
is_temp = True
def __init__(self, call_node, obj, method_name, args):
super(CachedBuiltinMethodCallNode, self).__init__(
call_node.pos,
obj=obj, method_name=method_name, args=args,
may_return_none=call_node.may_return_none,
type=call_node.type)
def may_be_none(self):
if self.may_return_none is not None:
return self.may_return_none
return ExprNode.may_be_none(self)
def generate_result_code(self, code):
arg_count = len(self.args)
obj_type = self.obj.type
call_func = '__Pyx_CallUnboundCMethod%d' % arg_count
utility_code_name = "CallUnboundCMethod%d" % arg_count
code.globalstate.use_utility_code(UtilityCode.load_cached(utility_code_name, "ObjectHandling.c"))
cache_cname = code.globalstate.get_cached_unbound_method(
obj_type.cname, self.method_name)
args = [self.obj] + self.args
code.putln("%s = %s(&%s, %s); %s" % (
self.result(),
call_func,
cache_cname,
', '.join(arg.py_result() for arg in args),
code.error_goto_if_null(self.result(), self.pos)
))
code.put_gotref(self.result())
class GeneralCallNode(CallNode):
# General Python function call, including keyword,
# * and ** arguments.
......
......@@ -2177,7 +2177,8 @@ class OptimizeBuiltinCalls(Visitor.NodeRefCleanupMixin,
attribute=attr_name,
is_called=True).analyse_as_type_attribute(self.current_env())
if method is None:
return node
return self._optimise_generic_builtin_method_call(
node, attr_name, function, arg_list, is_unbound_method)
args = node.args
if args is None and node.arg_tuple:
args = node.arg_tuple.args
......@@ -2193,6 +2194,18 @@ class OptimizeBuiltinCalls(Visitor.NodeRefCleanupMixin,
### builtin types
def _optimise_generic_builtin_method_call(self, node, attr_name, function, arg_list, is_unbound_method):
"""
Try to inject an unbound method call for a call to a method of a known builtin type.
This enables caching the underlying C function of the method at runtime.
"""
arg_count = len(arg_list)
if is_unbound_method or arg_count >= 3 or not function.type.is_pyobject:
return node
assert function.obj.type.is_builtin_type
return ExprNodes.CachedBuiltinMethodCallNode(
node, function.obj, attr_name, arg_list)
PyDict_Copy_func_type = PyrexTypes.CFuncType(
Builtin.dict_type, [
PyrexTypes.CFuncTypeArg("dict", Builtin.dict_type, None)
......
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