[Python-Dev] Patch to speed up non-tracing case in PyEval_EvalFrameEx (2% on pybench) (original) (raw)

Jeffrey Yasskin jyasskin at gmail.com
Mon Dec 1 02:54:02 CET 2008


Tracing support shows up fairly heavily an a Python profile, even though it's nearly always turned off. The attached patch against the trunk speeds up PyBench by 2% for me. All tests pass. I have 2 questions:

  1. Can other people corroborate this speedup on their machines? I'm running on a Macbook Pro (Intel Core2 processor, probably Merom) with a 32-bit build from Apple's gcc-4.0.1. (Apple's gcc consistently produces a faster python than gcc-4.3.)

  2. Assuming this speeds things up for most people, should I check it in anywhere besides the trunk? I assume it's out for 3.0; is it in for 2.6.1 or 3.0.1?

Pybench output:


PYBENCH 2.0


Benchmark: pybench.out

Rounds: 10
Warp:   10
Timer:  time.time

Machine Details:
   Platform ID:    Darwin-9.5.0-i386-32bit
   Processor:      i386

Python:
   Implementation: CPython
   Executable:

/Users/jyasskin/src/python/trunk-fast-tracing/build/python.exe Version: 2.7.0 Compiler: GCC 4.0.1 (Apple Inc. build 5488) Bits: 32bit Build: Nov 30 2008 17:14:10 (#trunk:67458M) Unicode: UCS2


Comparing with: ../build_orig/pybench.out

Rounds: 10
Warp:   10
Timer:  time.time

Machine Details:
   Platform ID:    Darwin-9.5.0-i386-32bit
   Processor:      i386

Python:
   Implementation: CPython
   Executable:

/Users/jyasskin/src/python/trunk-fast-tracing/build_orig/python.exe Version: 2.7.0 Compiler: GCC 4.0.1 (Apple Inc. build 5488) Bits: 32bit Build: Nov 30 2008 13:51:09 (#trunk:67458) Unicode: UCS2

Test minimum run-time average run-time this other diff this other diff

      BuiltinFunctionCalls:   127ms   130ms   -2.4%   129ms   132ms   -2.1%
       BuiltinMethodLookup:    90ms    93ms   -3.2%    91ms    94ms   -3.1%
             CompareFloats:    88ms    91ms   -3.3%    89ms    93ms   -4.3%
     CompareFloatsIntegers:    97ms    99ms   -2.1%    97ms   100ms   -2.4%
           CompareIntegers:    79ms    82ms   -4.2%    79ms    85ms   -6.1%
    CompareInternedStrings:    90ms    92ms   -2.4%    94ms    94ms   -0.9%
              CompareLongs:    86ms    83ms   +3.6%    87ms    84ms   +3.5%
            CompareStrings:    80ms    82ms   -3.1%    81ms    83ms   -2.3%
            CompareUnicode:   103ms   105ms   -2.3%   106ms   108ms   -1.5%
ComplexPythonFunctionCalls:   139ms   137ms   +1.3%   140ms   139ms   +0.1%
             ConcatStrings:   142ms   151ms   -6.0%   156ms   154ms   +1.1%
             ConcatUnicode:    87ms    92ms   -5.4%    89ms    94ms   -5.7%
           CreateInstances:   142ms   144ms   -1.4%   144ms   145ms   -1.1%
        CreateNewInstances:   107ms   109ms   -2.3%   108ms   111ms   -2.1%
   CreateStringsWithConcat:   114ms   137ms  -17.1%   117ms   139ms  -16.0%
   CreateUnicodeWithConcat:    92ms   101ms   -9.2%    95ms   102ms   -7.2%
              DictCreation:    77ms    81ms   -4.4%    80ms    85ms   -5.9%
         DictWithFloatKeys:    91ms   107ms  -14.5%    93ms   109ms  -14.6%
       DictWithIntegerKeys:    95ms    94ms   +1.4%   108ms    96ms  +12.3%
        DictWithStringKeys:    83ms    88ms   -5.8%    84ms    88ms   -4.7%
                  ForLoops:    72ms    72ms   -0.1%    79ms    74ms   +5.8%
                IfThenElse:    83ms    80ms   +3.9%    85ms    80ms   +5.3%
               ListSlicing:   117ms   118ms   -0.7%   118ms   121ms   -1.8%
            NestedForLoops:   116ms   119ms   -2.4%   121ms   121ms   +0.0%
      NormalClassAttribute:   106ms   115ms   -7.7%   108ms   117ms   -7.7%
   NormalInstanceAttribute:    96ms    98ms   -2.3%    97ms   100ms   -3.1%
       PythonFunctionCalls:    92ms    95ms   -3.7%    94ms    99ms   -5.2%
         PythonMethodCalls:   147ms   147ms   +0.1%   152ms   149ms   +2.1%
                 Recursion:   135ms   136ms   -0.3%   140ms   144ms   -2.9%
              SecondImport:   101ms    99ms   +2.1%   103ms   101ms   +2.2%
       SecondPackageImport:   107ms   103ms   +3.5%   108ms   104ms   +3.3%
     SecondSubmoduleImport:   134ms   134ms   +0.3%   136ms   136ms   -0.0%
   SimpleComplexArithmetic:   105ms   111ms   -5.0%   110ms   112ms   -1.4%
    SimpleDictManipulation:    95ms   106ms  -10.6%    96ms   109ms  -12.0%
     SimpleFloatArithmetic:    90ms    99ms   -9.3%    93ms   102ms   -8.2%
  SimpleIntFloatArithmetic:    78ms    76ms   +2.3%    79ms    77ms   +2.0%
   SimpleIntegerArithmetic:    78ms    77ms   +1.8%    79ms    77ms   +2.0%
    SimpleListManipulation:    80ms    78ms   +2.4%    80ms    79ms   +1.9%
      SimpleLongArithmetic:   110ms   113ms   -2.0%   111ms   113ms   -2.1%
                SmallLists:   128ms   117ms   +9.5%   130ms   124ms   +4.9%
               SmallTuples:   115ms   114ms   +1.7%   117ms   114ms   +2.2%
     SpecialClassAttribute:   101ms   112ms  -10.3%   104ms   114ms   -8.9%
  SpecialInstanceAttribute:   173ms   177ms   -1.9%   176ms   179ms   -1.6%
            StringMappings:   165ms   167ms   -1.2%   168ms   169ms   -0.5%
          StringPredicates:   126ms   134ms   -5.7%   127ms   134ms   -5.6%
             StringSlicing:   125ms   123ms   +1.9%   131ms   130ms   +0.7%
                 TryExcept:    79ms    80ms   -0.6%    80ms    80ms   -0.8%
                TryFinally:   110ms   107ms   +3.0%   111ms   112ms   -1.1%
            TryRaiseExcept:    99ms   101ms   -1.6%   100ms   102ms   -1.7%
              TupleSlicing:   127ms   127ms   +0.6%   137ms   137ms   +0.0%
           UnicodeMappings:   144ms   144ms   -0.3%   145ms   145ms   -0.4%
         UnicodePredicates:   116ms   114ms   +1.3%   117ms   115ms   +1.1%
         UnicodeProperties:   106ms   102ms   +3.6%   107ms   104ms   +3.1%
            UnicodeSlicing:    95ms   111ms  -14.0%    99ms   112ms  -11.8%
               WithFinally:   157ms   152ms   +3.3%   159ms   154ms   +3.3%
           WithRaiseExcept:   123ms   125ms   -1.1%   125ms   126ms   -1.2%

Totals: 6043ms 6182ms -2.2% 6185ms 6301ms -1.9%

(this=pybench.out, other=../build_orig/pybench.out)

2to3 times:

Before: $ time ./python.exe ~/src/2to3/2to3 -f all ~/src/2to3/ >/dev/null real 0m56.685s user 0m55.620s sys 0m0.380s

After: $ time ./python.exe ~/src/2to3/2to3 -f all ~/src/2to3/ >/dev/null real 0m55.067s user 0m53.843s sys 0m0.376s

== 3% faster

Gory details:

The meat of the patch is: @@ -884,11 +891,12 @@ fast_next_opcode: f->f_lasti = INSTR_OFFSET();

     /* line-by-line tracing support */

This converts the generated assembly (produced with gcc -S -dA ..., then manually annotated a bit) from:

# basic block 17
# ../Python/ceval.c:885

LM541: movl 8(%ebp), %ecx LVL319: subl -316(%ebp), %edx movl %edx, 60(%ecx) # ../Python/ceval.c:889 LM542:

%esi = tstate

movl	-336(%ebp), %esi

LVL320:

%eax = tstate->c_tracefunc

movl	28(%esi), %eax

LVL321:

if tstate->c_tracefunc == 0

testl	%eax, %eax

goto past-if ()

je	L567

more if conditions here

to:

# basic block 17
# ../Python/ceval.c:889

LM542: movl 8(%ebp), %ecx LVL319: subl -316(%ebp), %edx movl %edx, 60(%ecx) # ../Python/ceval.c:893 LM543:

%eax = _Py_TracingPossible

movl	__Py_TracingPossible-"L00000000033$pb"(%ebx), %eax

LVL320:

if _Py_TracingPossible != 0

testl	%eax, %eax

goto rest-of-if (nearby)

jne	L2321

opcode = NEXTOP(); continues here

The branch should be predicted accurately either way, so there are 2 things that may be contributing to the performance change.

First, adding the global caching variable halves the amount of memory that has to be read to check the prediction. The memory that is read is still read one instruction before it's used, but adding a local variable to read the memory earlier doesn't affect the performance.

Without the global variable, the compiler puts the tracing code immediately after the if; with the global, it moves it away and puts the non-tracing code immediately after the first test in the if. This may affect branch prediction and may affect the icache. I tried using gcc's __builtin_expect() to ensure that the tracing code is always out-of-line. This moved it much farther away and cost about 1% in performance (i.e. 1% instead of 2% faster than "before"). I don't know why the __builtin_expect() version would be slower. If anyone feels inspired to test this out on another processor or compiler version, let me know how it goes.

Jeffrey -------------- next part -------------- A non-text attachment was scrubbed... Name: fast-tracing.diff Type: application/octet-stream Size: 1658 bytes Desc: not available URL: <http://mail.python.org/pipermail/python-dev/attachments/20081130/1a858073/attachment.obj>



More information about the Python-Dev mailing list