[Python-3000] Speeding up function calls

Talin talin at acm.org
Sat Apr 29 21:09:36 CEST 2006


In the current implementation, a function call starts by gathering 4
collections of arguments:

  -- The positional arguments, which are in a flat array
  -- The keyword arguments, which are in a flat array of
     keyword/value pairs.
  -- The varargs argument, which is a reference to a sequence.
  -- The keyword dictionary argument, which is a reference to a
     mapping.

The function calling machinery starts by merging these various parts into two
containers: A tuple and a dict.

  -- The tuple consists of the concatenation of the positional
     argument array with the varargs argument sequence.
  -- The dict consists of the union of the keyword argument array and
     the keyword dictionary array, with the restriction that duplicate
     keys are an error.

Once control is transferred to the actual function, the arguments are
unpacked using the specified argument unpacking logic - i.e. assigning
argument values to formal parameter slots.

My proposal is that we skip the middle portion of this process, and
pass the initial 4 collections directly to the called function.

Thus, the PyObject_Call function will look something like this:

  PyObject_Call( PyObject **positionalArgs, int numPositionalArgs,
                 PyObject **keywordArgs, int numKeywordArgs,
                 PyTuple *varargs,
                 PyDict *kwdictargs );

(Alternatively, the various parameters could be passed in a single
struct argument.)

The calling function, upon recieving these arguments, can do a number
of different things:

1) It can go ahead and construct the tuple/dict pair used in the
previous implementation.

2) It can attempt to assign the arguments directly to parameter slots.
This is fairly straightforward, because the current slot assignment
algorithm is a one-pass algorithm that sequentially visits each input
argument.

For positional arguments, instead of iterating once over a PyTuple,
the algorithm would iterate over the positional args array, and the
iterate over the varargs argument. Keyword arguments would be handled
similarly - iterate over the keyword/value pair array, and then
iterate over the dict.

3) It can attempt to use the input arguments directly. For example, a
function that accepts only positional arguments would do the
following:

    # To use positional argument 'n':
    value = n < numPositionalArgs
       ? positionalArgs[ n ] : varargs[ n - numPositionalArgs ]

4) It can do some combination of the above.

The decision as to how to handle the arguments will be made by the
compiler as an optimization decision, based on the function signature.

For example, if a function contains no formal **kwargs parameter, then
part of the slot assignment algorithm can be skipped. On the other
hand, if the function only has a **kwargs parameter, and no other
keyword parameters, then a different part of the processing can be
skipped.

In other words, moving the argument packing logic into the caller
doesn't by itself speed up function calling; What it does is open up a
broad range of optimization possibilities. The simpler the recieving
function's signature, the more room the compiler will have to be able
to cull out unneeded parts of the argument assignment logic.

-- Talin




More information about the Python-3000 mailing list