Showing posts with label performance. Show all posts
Showing posts with label performance. Show all posts

Wednesday, December 3, 2008

cinpy - or C in Python

cinpy is a tool where you can write C code in your Python code (with the help of ctypes - included in modern Python versions). When you execute your python program the C code is compiled on the fly using Tiny C Compiler (TCC).
In this posting I will describe:
1) installation and testing cinpy
2) a simple benchmark (c-in-py vs python)
3) compare performance with gcc (c-in-py vs gcc)
4) measure cinpy (on-the-fly) compilation time
5) how to dynamically change cinpy methods

1. How to install and try cinpy (note: also found in cinpy/tcc README files)
1) download, uncompress and compile TCC
./configure
make
make install
gcc -shared -Wl,-soname,libtcc.so -o libtcc.so libtcc.o

2) download, uncompress and try cinpy
cp ../tcc*/*.so .
python cinpy_test.py  # you may have to comment out or install psyco 

2. Sample performance results (on a x86 linux box):

python cinpy_test.py
Calculating fib(30)...
fibc : 1346269 time: 0.03495 s
fibpy: 1346269 time: 2.27871 s
Calculating for(1000000)...
forc : 1000000 time: 0.00342 s
forpy: 1000000 time: 0.32119 s

Using cinpy for fibc (Fibonacci) method was ~65 times faster than fibpy, and and cinpy for forc (loop) was ~93 times faster than forpy, not bad.

3. How does cinpy (compiled with tcc) compare to gcc performance?
Copying the C fib() method and calling it with main program
$ time fibgcc


fib(30) = 1346269

real    0m0.016s
user    0m0.020s
sys     0m0.000s


GCC gives roughly twice as fast code as cinpy/tcc (0.034/0.016). 




4. How long time does it take for tcc to on-the-fly compile cinpy methods?


#!/usr/bin/env python
# cinpy_compile_performance.py
import ctypes
import cinpy
import time
t=time.time
t0 = t()
fibc=cinpy.defc(
     "fib",
     ctypes.CFUNCTYPE(ctypes.c_int,ctypes.c_int),
     """
     int fib(int x) {
       if (x<=1) return 1;
       return fib(x-1)+fib(x-2);
     }
     """)
t1 = t()
print "Calculating fib(30)..."
sc,rv_fibc,ec=t(),fibc(30),t()
print "fibc :",rv_fibc,"time: %6.5f s" % (ec-sc)
print "compilation time = %6.5f s" % (t1-t0)

python cinpy_compile_performance.py

Calculating fib(30)...
fibc: 1346269 time: 0.03346 s
compilation time: 0.00333 s


So compilation (and linking) time is about 3.3ms, which is reasonably good, not a lot of overhead! (note: TCC is benchmarked to compile, assemble and link 67MB of code in 2.27s)


5. "Hot-swap" replacement of cinpy code?
Let us assume you have a system with a "pareto" situation, i.e. 80-99% of the code doesn't need be fast (written in Python), but 1-20% need to be really high performance (and written in C using cinpy), and that you need to frequently change the high performing code, can that be done? Examples of such a system could be mobile (software) agents.


Sure, all you need to do is wrap your cinpy definition as a string and run exec on it, like this:

origmethod="""
fibc=cinpy.defc("fib",ctypes.CFUNCTYPE(ctypes.c_int,ctypes.c_int),
          '''
          int fib(int x) {
              if (x<=1) return 1;
              return fib(x-1)+fib(x-2);
          }
          ''')
"""
# add an offset to the Fibonacci method
alternatemethod = origmethod.replace("+", "+998+")
print alternatemethod
# alternatemethod has replaces origmethod with exec(alternatemethod)
print fibc(2) # = 1000
Conclusion:
cinpy ain't bad.

Remark: depending on the problem you are solving (e.g. if it is primarily network IO bound and not CPU bound) , becoming 1-2 orders of magnitude faster (cinpy vs pure python) is probably fast enough (CPU wise), the doubling from GCC may not matter (since network IO wise Python performs quite alright, e.g. with Twisted ). 

Saturday, November 29, 2008

Tools for Accelerating Python

If you need to speed up your Python program there are several possible approaches, with varying degree of effort needed, here is (probably not complete) overview:
  1. Rewrite your Python code by parallelizing or optimizing/replacing/tuning algorithm(s), e.g. using: 
  2. Use a tool that can speed up your code more or less unchanged
    • Psyco
      • Just in time compiler, note: this is probably the easiest approach to try
    • Pyrex
      • Write and compile Python with a flavor of C data structures
    • Cython 
    • PyJs 
      • Compile (large subset of) Python to Javascript, note: probably more interesting for client development (ajax) than server side.
    • Rpython
      • Compile (large subset of) Python to native code, note: part of PyPy project
    • PyStream
    • GPULib
    • Shedskin 
  3. Replace (parts of) your Python code with another language

Tuesday, January 29, 2008

Stackless RPython - Recursive

Got input from pypy/rpython developers, and in order to get stackless rpython it was just to add parameter stackless=True and replace parameter gc='ref' with gc='generation' to Translation() method.
# The Computer Language Shootout
# http://shootout.alioth.debian.org/
# based on bearophile's psyco program
# slightly modified by Isaac Gouy
# And adapted to RPython by Amund

def Ack(x, y):
if x == 0: return y+1
if y == 0: return Ack(x-1, 1)
return Ack(x-1, Ack(x, y-1))

def Fib(n):
if n < 2: return 1
return Fib(n-2) + Fib(n-1)

def FibFP(n):
if n < 2.0: return 1.0
return FibFP(n-2.0) + FibFP(n-1.0)

def Tak(x, y, z):
if y < x:
return Tak(
Tak(x-1,y,z),
Tak(y-1,z,x),
Tak(z-1,x,y) )
return z

def TakFP(x, y, z):
if y < x:
return TakFP(
TakFP(x-1.0,y,z),
TakFP(y-1.0,z,x),
TakFP(z-1.0,x,y) )
return z

# RPython stuff starts here

from sys import argv #, setrecursionlimit
#setrecursionlimit(12345678901)

def main(argv):
n = int(argv[1]) - 1
print "Ack(3,%d):" % (n+1), Ack(3, n+1)
print "Fib(" + str(28.0+n) + "," + str(FibFP(28.0+n))
print "Tak(%d,%d,%d): %d" % (3*n, 2*n, n, Tak(3*n, 2*n, n))
print "Fib(3):", Fib(3)
print "Tak(3.0,2.0,1.0):", TakFP(3.0, 2.0, 1.0)
return 0

from pypy.translator.interactive import Translation
#t = Translation(main, standalone=True, gc='ref')
t = Translation(main, standalone=True,
stackless=True, gc='generation')
t.source(backend='c')
path = t.compile()
print path

Monday, January 28, 2008

RPython GCLB Benchmark - Recursive

Must admit that I am a big fan of python (the programming language), and when I saw the benchmark that RPython can be faster than C (on the binary tree benchmark from the Great Computer Language Shootout - GCLS), I just had to try RPython on another problem from GCLS, so I chose the one with worst performance compared to C/gcc (266 times slower) - recursive (with various recursive methods, e.g. Ackerman, Fibonacci and Tak). Results were roughly that rpython was 50-100 and gcc/c was 100-300 times faster than python (note: I only did one run, so numbers can be somewhat bogus, but not too bad I think).



Unfortunately for the run with n=11 Ackerman had consumed all the stack, but that can be solved with Stackless RPython.