Merged revisions 65240-65242 via svnmerge from
authorAntoine Pitrou <solipsis@pitrou.net>
Fri, 25 Jul 2008 22:39:39 +0000 (22:39 +0000)
committerAntoine Pitrou <solipsis@pitrou.net>
Fri, 25 Jul 2008 22:39:39 +0000 (22:39 +0000)
svn+ssh://pythondev@svn.python.org/python/trunk

........
  r65240 | antoine.pitrou | 2008-07-26 00:02:07 +0200 (sam., 26 juil. 2008) | 3 lines

  add a pybench test for complex function calls (part of #1819)
........
  r65241 | antoine.pitrou | 2008-07-26 00:13:52 +0200 (sam., 26 juil. 2008) | 4 lines

  Raymond's patch for #1819: speedup function calls with named parameters
  (35% faster according to pybench)
........
  r65242 | antoine.pitrou | 2008-07-26 00:22:08 +0200 (sam., 26 juil. 2008) | 3 lines

  add a NEWS entry
........

Misc/NEWS
Python/ceval.c
Tools/pybench/Calls.py

index 76ecbccf81f25b6dd55dc0c74811170e50137e32..0ef8b05ceede01d80cb7b3a698d71abc39dce376 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -9,6 +9,12 @@ What's new in Python 3.0b3?
 
 *Release date: XX-XXX-2008*
 
+Core and Builtins
+-----------------
+
+- Issue #1819: function calls with several named parameters are now on
+  average 35% faster (as measured by pybench).
+
 Library
 -------
 
index 9601de57711e398c34e7d00645f9ccf5125b5bdc..af7a67a6d7a6d2f28039d6767126aacb0900488c 100644 (file)
@@ -642,9 +642,9 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
        processor's own internal branch predication has a high likelihood of
        success, resulting in a nearly zero-overhead transition to the
        next opcode.  A successful prediction saves a trip through the eval-loop
-       including its two unpredictable branches, the HAS_ARG test and the 
+       including its two unpredictable branches, the HAS_ARG test and the
        switch-case.  Combined with the processor's internal branch prediction,
-       a successful PREDICT has the effect of making the two opcodes run as if 
+       a successful PREDICT has the effect of making the two opcodes run as if
        they were a single new opcode with the bodies combined.
 
     If collecting opcode statistics, your choices are to either keep the
@@ -796,7 +796,7 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
                           an argument which depends on the situation.
                           The global trace function is also called
                           whenever an exception is detected. */
-                       if (call_trace_protected(tstate->c_tracefunc, 
+                       if (call_trace_protected(tstate->c_tracefunc,
                                                 tstate->c_traceobj,
                                                 f, PyTrace_CALL, Py_None)) {
                                /* Trace function raised an error */
@@ -828,10 +828,10 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
           this wasn't always true before 2.3!  PyFrame_New now sets
           f->f_lasti to -1 (i.e. the index *before* the first instruction)
           and YIELD_VALUE doesn't fiddle with f_lasti any more.  So this
-          does work.  Promise. 
+          does work.  Promise.
 
           When the PREDICT() macros are enabled, some opcode pairs follow in
-           direct succession without updating f->f_lasti.  A successful 
+           direct succession without updating f->f_lasti.  A successful
            prediction effectively links the two codes together as if they
            were a single new opcode; accordingly,f->f_lasti will point to
            the first code in the pair (for instance, GET_ITER followed by
@@ -1678,7 +1678,7 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
                {
                        int totalargs = 1 + (oparg & 0xFF) + (oparg >> 8);
                        v = POP();
-                       
+
                        if (unpack_iterable(v, oparg & 0xFF, oparg >> 8,
                                            stack_pointer + totalargs)) {
                                stack_pointer += totalargs;
@@ -2071,7 +2071,7 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
                            because it prevents detection of a control-break in tight loops like
                            "while 1: pass".  Compile with this option turned-on when you need
                            the speed-up and do not need break checking inside tight loops (ones
-                           that contain only instructions ending with goto fast_next_opcode). 
+                           that contain only instructions ending with goto fast_next_opcode).
                         */
                        goto fast_next_opcode;
 #else
@@ -2257,7 +2257,7 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
                    break;
                }
 
-               case MAKE_CLOSURE:              
+               case MAKE_CLOSURE:
                case MAKE_FUNCTION:
                {
                    int posdefaults = oparg & 0xff;
@@ -2267,7 +2267,7 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
                        v = POP(); /* code object */
                        x = PyFunction_New(v, f->f_globals);
                        Py_DECREF(v);
-                       
+
                        if (x != NULL && opcode == MAKE_CLOSURE) {
                                v = POP();
                                err = PyFunction_SetClosure(x, v);
@@ -2650,6 +2650,7 @@ PyEval_EvalCodeEx(PyCodeObject *co, PyObject *globals, PyObject *locals,
                        }
                }
                for (i = 0; i < kwcount; i++) {
+                       PyObject **co_varnames;
                        PyObject *keyword = kws[2*i];
                        PyObject *value = kws[2*i + 1];
                        int j;
@@ -2659,16 +2660,25 @@ PyEval_EvalCodeEx(PyCodeObject *co, PyObject *globals, PyObject *locals,
                                    co->co_name);
                                goto fail;
                        }
-                       /* XXX slow -- speed up using dictionary? */
+                       /* Speed hack: do raw pointer compares. As names are
+                          normally interned this should almost always hit. */
+                       co_varnames = PySequence_Fast_ITEMS(co->co_varnames);
+                       for (j = 0;
+                            j < co->co_argcount + co->co_kwonlyargcount;
+                            j++) {
+                               PyObject *nm = co_varnames[j];
+                               if (nm == keyword)
+                                       goto kw_found;
+                       }
+                       /* Slow fallback, just in case */
                        for (j = 0;
                             j < co->co_argcount + co->co_kwonlyargcount;
                             j++) {
-                               PyObject *nm = PyTuple_GET_ITEM(
-                                       co->co_varnames, j);
+                               PyObject *nm = co_varnames[j];
                                int cmp = PyObject_RichCompareBool(
                                        keyword, nm, Py_EQ);
                                if (cmp > 0)
-                                       break;
+                                       goto kw_found;
                                else if (cmp < 0)
                                        goto fail;
                        }
@@ -2685,20 +2695,20 @@ PyEval_EvalCodeEx(PyCodeObject *co, PyObject *globals, PyObject *locals,
                                        goto fail;
                                }
                                PyDict_SetItem(kwdict, keyword, value);
+                               continue;
                        }
-                       else {
-                               if (GETLOCAL(j) != NULL) {
-                                       PyErr_Format(PyExc_TypeError,
-                                            "%U() got multiple "
-                                            "values for keyword "
-                                            "argument '%S'",
-                                            co->co_name,
-                                            keyword);
-                                       goto fail;
-                               }
-                               Py_INCREF(value);
-                               SETLOCAL(j, value);
+kw_found:
+                       if (GETLOCAL(j) != NULL) {
+                               PyErr_Format(PyExc_TypeError,
+                                        "%U() got multiple "
+                                        "values for keyword "
+                                        "argument '%S'",
+                                        co->co_name,
+                                        keyword);
+                               goto fail;
                        }
+                       Py_INCREF(value);
+                       SETLOCAL(j, value);
                }
                if (co->co_kwonlyargcount > 0) {
                        for (i = co->co_argcount;
@@ -2930,7 +2940,7 @@ raise_error:
 
 /* Iterate v argcnt times and store the results on the stack (via decreasing
    sp).  Return 1 for success, 0 if error.
-   
+
    If argcntafter == -1, do a simple unpack. If it is >= 0, do an unpack
    with a variable target.
 */
index cfe07152639b565f3f1f97f47663ded6788c6e96..7c11867eae399127612e650800b323aaec72d1be 100644 (file)
@@ -109,6 +109,64 @@ class PythonFunctionCalls(Test):
 
 ###
 
+class ComplexPythonFunctionCalls(Test):
+
+    version = 2.0
+    operations = 4*5
+    rounds = 100000
+
+    def test(self):
+
+        # define functions
+        def f(a,b,c,d=1,e=2,f=3):
+            return f
+
+        args = 1,2
+        kwargs = dict(c=3,d=4,e=5)
+
+        # do calls
+        for i in range(self.rounds):
+            f(a=i,b=i,c=i)
+            f(f=i,e=i,d=i,c=2,b=i,a=3)
+            f(1,b=i,**kwargs)
+            f(*args,**kwargs)
+
+            f(a=i,b=i,c=i)
+            f(f=i,e=i,d=i,c=2,b=i,a=3)
+            f(1,b=i,**kwargs)
+            f(*args,**kwargs)
+
+            f(a=i,b=i,c=i)
+            f(f=i,e=i,d=i,c=2,b=i,a=3)
+            f(1,b=i,**kwargs)
+            f(*args,**kwargs)
+
+            f(a=i,b=i,c=i)
+            f(f=i,e=i,d=i,c=2,b=i,a=3)
+            f(1,b=i,**kwargs)
+            f(*args,**kwargs)
+
+            f(a=i,b=i,c=i)
+            f(f=i,e=i,d=i,c=2,b=i,a=3)
+            f(1,b=i,**kwargs)
+            f(*args,**kwargs)
+
+
+    def calibrate(self):
+
+        # define functions
+        def f(a,b,c,d=1,e=2,f=3):
+            return f
+
+        args = 1,2
+        kwargs = dict(c=3,d=4,e=5)
+
+        # do calls
+        for i in range(self.rounds):
+            pass
+
+###
+
 class BuiltinFunctionCalls(Test):
 
     version = 2.0