Author: Perrygeo
Translator: Rai Yonghao (http://laiyonghao.com)
Original: http://www.perrygeo.net/wordpress/?p=116
My favorite is Python, whose code is elegant and practical, but it is slower than most languages purely from a speed perspective. Most people also think that speed and ease of use are polar opposites-writing C code is really painful. And Cython tries to eliminate this duality and let you also have Python syntax and C data types and functions--all two of them are the best in the world. Remember, I am by no means my expert in this field, this is my first time to cython the real experience of the notes:
Editor: According to some of the feedback I received, everyone seems a bit confused--cython is used to generate C extensions rather than stand-alone programs. All acceleration is done for a function of an existing Python application. The entire application is not rewritten with C or Lisp, and there is no handwriting C extension. Just a simple way to integrate C's speed and C data types into Python functions.
We can now say that we can make the Great_circle function faster. The so-called great_circle is the question of calculating the distance between two points along the Earth's surface:
p1.py
Import Math
def great_circle (LON1,LAT1,LON2,LAT2):
Radius = 3956 #miles
x = math.pi/180.0
A = (90.0-LAT1) * (x)
b = (90.0-LAT2) * (x)
theta = (lon2-lon1) * (x)
c = Math.acos ((Math.Cos (a) *math.cos (b)) +
(Math.sin (a) *math.sin (b) *math.cos (theta))
Return Radius*c
Let's call it 500,000 times and measure its time:
Import Timeit
Lon1, LAT1, lon2, lat2 =-72.345, 34.323,-61.823, 54.826
num = 500000
t = Timeit. Timer ("P1.great_circle (%f,%f,%f,%f)"% (LON1,LAT1,LON2,LAT2),
"Import P1")
print "Pure python function", T.timeit (num), "SEC"
About 2.2 seconds. It's too slow!
Let's try to rewrite it quickly with Cython and see if there's a difference:
C1.pyx
Import Math
def great_circle (float lon1,float lat1,float lon2,float lat2):
cdef Float radius = 3956.0
cdef float pi = 3.14159265
cdef float x = pi/180.0
Cdef float A,b,theta,c
A = (90.0-LAT1) * (x)
b = (90.0-LAT2) * (x)
theta = (lon2-lon1) * (x)
c = Math.acos ((Math.Cos (a) *math.cos (b)) + (Math.sin (a) *math.sin (b) *math.cos (theta)))
Return Radius*c
Please note that we still import Math--cython lets you mix python and C data types to some extent. The conversion is automatic, but it is not without cost. What we do in this case is define a Python function, declare that its input parameter is a floating-point type, and declare the type C floating-point data type for all variables. In the calculation section it still uses the Python math module.
Now we need to convert it to C code and compile to Python extensions. The best way to do this is to write a script called setup.py. But now we use the manual method to understand the witchcraft in it:
# This'll create a c1.c file-the C source code to build a python extension
Cython C1.pyx
# Compile The object file
Gcc-c-fpic-i/usr/include/python2.5/c1.c
# Link it into a shared library
gcc-shared C1.o-o c1.so
Now you should have a c1.so (or. dll) file that can be import by Python. Now run:
t = Timeit. Timer ("C1.great_circle (%f,%f,%f,%f)"% (LON1,LAT1,LON2,LAT2),
"Import C1")
Print "Cython function (still using Python math)", T.timeit (num), "s
About 1.8 seconds. It's not the kind of big performance improvement we started expecting. Using the Python match module should be a bottleneck. Now let's replace it with the C standard library:
C2.pyx
cdef extern from "MATH.H":
float COSF (float theta)
float Sinf (float theta)
float ACOSF (float theta)
def great_circle (float lon1,float lat1,float lon2,float lat2):
cdef Float radius = 3956.0
cdef float pi = 3.14159265
cdef float x = pi/180.0
Cdef float A,b,theta,c
A = (90.0-LAT1) * (x)
b = (90.0-LAT2) * (x)
theta = (lon2-lon1) * (x)
c = ACOSF ((COSF (a) *COSF (b)) + (Sinf (a) *sinf (b) *cosf (theta)))
Return RADIUS*CEC "
As with import math, we use cdef extern to declare functions from the specified header file (this is the math.h that uses the C standard library). Instead of a costly Python function, we then set up a new shared library and test it again:
t = Timeit. Timer ("C2.great_circle (%f,%f,%f,%f)"% (LON1,LAT1,LON2,LAT2),
"Import C2")
Print "Cython function (using trig function from MATH.H)", T.timeit (num), sec
Do you like it a little now? 0.4 seconds-increases 5 times times faster than pure Python functions. What else can we do to improve our speed? C2.great_circle () is still a Python function call, which means that it generates the overhead of the Python API (building parameter tuples, etc.), and if we can write a pure C function, we might be able to speed it up.
C3.pyx
cdef extern from "MATH.H":
float COSF (float theta)
float Sinf (float theta)
float ACOSF (float theta)
Cdef float _great_circle (float lon1,float lat1,float lon2,float lat2):
cdef Float radius = 3956.0
cdef float pi = 3.14159265
cdef float x = pi/180.0
Cdef float A,b,theta,c
A = (90.0-LAT1) * (x)
b = (90.0-LAT2) * (x)
theta = (lon2-lon1) * (x)
c = ACOSF ((COSF (a) *COSF (b)) + (Sinf (a) *sinf (b) *cosf (theta)))
Return Radius*c
def great_circle (float lon1,float lat1,float lon2,float lat2,int num):
cdef int I
Cdef float X
For i from 0 < = i < num:
x = _great_circle (LON1,LAT1,LON2,LAT2)
return x
Notice that we still have a Python function (DEF) that accepts num for an extra parameter. The loop in this function uses for i from 0 < = i < num: instead of more pythonic, but much slower for I in range (num):. The real calculation is done in the C function (CDEF), which returns the float type. This version takes only 0.2 seconds--10 times times faster than the original Python function.
To prove that we have done enough optimization, we can write a small application in pure C and then measure the time:
#include <math .h>
#include <stdio .h>
#define NUM 500000
Float great_circle (float lon1, float lat1, float lon2, float lat2) {
float radius = 3956.0;
float pi = 3.14159265;
float x = pi/180.0;
float A,b,theta,c;
A = (90.0-LAT1) * (x);
b = (90.0-LAT2) * (x);
theta = (lon2-lon1) * (x);
c = ACOs ((cos (a) *cos (b)) + (sin (a) *sin (b) *cos (theta)));
return radius*c;
}
int main () {
int i;
float x;
for (i=0 i < = NUM; i++)
x = great_circle (-72.345, 34.323,-61.823, 54.826);
printf ("%f", X);
}
Compile it with Gcc-lm-o ctest ctest.c, test with time .../ctest ... About 0.2 seconds. This gives me the confidence that my Cython expansion phase is also very efficient for my C code (not that my C programming ability is very weak).
How much performance can be optimized with Cython usually depends on how many loops, numeric operations, and Python function calls, which can slow down the program. There have been reports of a 100 to 1000 times-fold increase in some cases. As for other tasks, it may not be that useful. Remember this before you frantically rewrite the Python code with Cython:
"We should forget the small efficiency, the premature optimization is the root of all evils, there are 97% of cases." "--donald Knuth
In other words, write the program in Python and see if it meets your needs. In most cases, its performance is good enough ... But sometimes really feel slow, then use the parser to find the bottleneck function, and then rewrite with Cython, can quickly get higher performance.
External links
Worldmill (Http://trac.gispython.org/projects/PCL/wiki/WorldMill)--a fast, concise, Python-interface module written by Sean Gillies with Cython , encapsulates a libgdal library for processing vector geospatial data.
Writing faster Pyrex code (HTTP://WWW.SAGEMATH.ORG:9001/WRITINGFASTPYREXCODE)--pyrex is the predecessor of Cython, which has similar goals and syntax.