Hi,

I did a tiny bit of profiling on Cython compiling lxml.etree. Here are the
numbers I get:

"""
         3127018 function calls (2777951 primitive calls) in 25.128 CPU seconds

   Ordered by: internal time, call count
   List reduced from 1035 to 20 due to restriction <20>

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
   119144    4.579    0.000    4.671    0.000 
Scanners.py:148(run_machine_inlined)
    18316    1.586    0.000    1.586    0.000 codecs.py:371(read)
   119144    1.074    0.000    5.746    0.000 Scanners.py:109(scan_a_token)
    88362    1.023    0.000    7.976    0.000 Scanners.py:88(read)
    29680    0.673    0.000    1.659    0.000 Code.py:103(mark_pos)
       77    0.588    0.008    0.588    0.008 posixpath.py:168(exists)
    88362    0.538    0.000    8.514    0.000 Scanning.py:397(next)
23318/2985    0.495    0.000    0.517    0.000 Nodes.py:155(end_pos)
    70446    0.442    0.000    0.591    0.000 Code.py:62(put)
90657/10562    0.351    0.000    4.549    0.000 Parsing.py:59(p_binop_expr)
[...]
"""

So the major headache here is Scanners.py in Plex. The method at the top-rank
is a huge function. According to the comments, it's the result of inlining a
couple of method calls that originally lead to slow code, and it looks heavily
profiled already.

Assuming that further optimisation attempts were rather futile, I just
compiled the module with Cython. The first (obvious) result is that the
internal calls disappear from the profile log, as they are now internal C
calls. The call that remains is Scanner.next(), which originally took an
accumulated 8.5 seconds. In the compiled version, it's down to just over 5
seconds, that's more than 40 percent faster.

"""
         2595627 function calls (2246560 primitive calls) in 18.681 CPU seconds

   Ordered by: internal time, call count
   List reduced from 1028 to 20 due to restriction <20>

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    88362    4.246    0.000    5.041    0.000 Scanning.py:397(next)
    29680    0.673    0.000    1.632    0.000 Code.py:103(mark_pos)
    70446    0.439    0.000    0.586    0.000 Code.py:62(put)
90657/10562    0.335    0.000    3.228    0.000 Parsing.py:59(p_binop_expr)
23318/2985    0.316    0.000    0.338    0.000 Nodes.py:155(end_pos)
    65791    0.295    0.000    0.903    0.000 Code.py:47(putln)
    29677    0.289    0.000    0.915    0.000 Code.py:93(file_contents)
     4724    0.287    0.000    0.292    0.000 Symtab.py:532(allocate_temp)
    88070    0.247    0.000    0.247    0.000 ExprNodes.py:192(subexpr_nodes)
    52071    0.232    0.000    0.232    0.000 Nodes.py:82(__init__)
[...]
"""

In total, I get an improvement of 12% in compilation time. That makes me think
that it's actually worth putting the compilation into Cython's own setup.py,
and installing the compiled Scanning module next to the Python one (Python
prefers C extensions on import). Here's a patch, what do you think?

Stefan


# HG changeset patch
# User Stefan Behnel <[EMAIL PROTECTED]>
# Date 1212929816 -7200
# Node ID 5562d59529350243c8d7d1130e0f3541f2c5eb9e
# Parent  fb47d44008f43174c14cc2c446d6bfb3bf85ce29
compile Cython.Plex.Scanners module on install to speed up compilation

diff -r fb47d44008f4 -r 5562d5952935 setup.py
--- a/setup.py	Sat Jun 07 10:45:33 2008 -0700
+++ b/setup.py	Sun Jun 08 14:56:56 2008 +0200
@@ -1,6 +1,6 @@ from distutils.core import setup
-from distutils.core import setup
+from distutils.core import setup, Extension
 from distutils.sysconfig import get_python_lib
-import os, sys
+import os, os.path
 import sys
 from Cython.Compiler.Version import version
 
@@ -21,6 +21,31 @@ if os.name == "posix":
     scripts = ["bin/cython"]
 else:
     scripts = ["cython.py"]
+
+try:
+    sys.argv.remove("--no-compile")
+except ValueError:
+    try:
+        from Cython.Compiler.Main import compile
+        source_root = os.path.dirname(__file__)
+        compiled_modules = ["Cython.Plex.Scanners"]
+        extensions = []
+        for module in compiled_modules:
+            source_file = os.path.join(source_root, *module.split('.'))
+            print("Compiling module %s ..." % module)
+            result = compile(source_file + ".py")
+            if result.c_file:
+                extensions.append(
+                    Extension(module, sources = [result.c_file])
+                    )
+            else:
+                print("Compilation failed")
+        if extensions:
+            setup_args['ext_modules'] = extensions
+    except Exception:
+        print("ERROR: %s" % sys.exc_info()[1])
+        print("Extension module compilation failed, using plain Python implementation")
+
 
 setup(
   name = 'Cython', 
_______________________________________________
Cython-dev mailing list
[email protected]
http://codespeak.net/mailman/listinfo/cython-dev

Reply via email to