# PythonとPython C extensionの速度比較

スポンサーリンク

## Writing Python/C extensions by hand¶

ctypesインターフェイスはすぐに書けるが、必ずしも常にPythonよりも高速であるとは限らない。cos関数をPythonのmathライブラリとlibmをctypesバインディングを介してコールする方法を比べてみる。

import math, time, ctypes

R = range(100000)

libc = ctypes.CDLL("libm.so.6", ctypes.RTLD_GLOBAL)
libc.cos.argtypes = [ctypes.c_double]
libc.cos.restype = ctypes.c_double

def do_timings(cos):
t1 = time.time()
for x in R:
cos(0.0)
return time.time()-t1

def do_math_cos():
return do_timings(math.cos)

def do_libc_cos():
return do_timings(libc.cos)

print ("math.cos", do_math_cos())
print ("libc.cos", do_libc_cos())
math.cos 0.01768660545349121
libc.cos 0.04040241241455078

スポンサーリンク

## Drawing a Mandelbrot set¶

お馴染みのマンデルブロ集合描画をやる。以下にそのソースコードお載せる。

#include "Python.h"

/* make this static if you don't want other code to call this function */
/* I don't make it static because want to access this via ctypes */
/* static */
int iterate_point(double x0, double y0, int max_iterations) {
int iteration = 0;
double x=x0, y=y0, x2=x*x, y2=y*y;

while (x2+y2<4 && iteration<max_iterations) {
y = 2*x*y + y0;
x = x2-y2 + x0;
x2 = x*x;
y2 = y*y;
iteration++;
}
return iteration;
}

/* The module doc string */
PyDoc_STRVAR(mandelbrot__doc__,
"Mandelbrot point evalutation kernel");

/* The function doc string */
PyDoc_STRVAR(iterate_point__doc__,
"x,y,max_iterations -> iteration count at that point, up to max_iterations");

/* The wrapper to the underlying C function */
static PyObject *
py_iterate_point(PyObject *self, PyObject *args)
{
double x=0, y=0;
int iterations, max_iterations=1000;
/* "args" must have two doubles and may have an integer */
/* If not specified, "max_iterations" remains unchanged; defaults to 1000 */
/* The ':iterate_point' is for error messages */
if (!PyArg_ParseTuple(args, "dd|i:iterate_point", &x, &y, &max_iterations))
return NULL;
/* Verify the parameters are correct */
if (max_iterations < 0) max_iterations = 0;

/* Call the C function */
iterations = iterate_point(x, y, max_iterations);

/* Convert from a C integer value to a Python integer instance */
return PyInt_FromLong((long) iterations);
}

/* A list of all the methods defined by this module. */
/* "iterate_point" is the name seen inside of Python */
/* "py_iterate_point" is the name of the C function handling the Python call */
/* "METH_VARGS" tells Python how to call the handler */
/* The {NULL, NULL} entry indicates the end of the method definitions */
static PyMethodDef mandelbrot_methods[] = {
{"iterate_point",  py_iterate_point, METH_VARARGS, iterate_point__doc__},
{NULL, NULL}      /* sentinel */
};

/* When Python imports a C module named 'X' it loads the module */
/* then looks for a method named "init"+X and calls it.  Hence */
/* for the module "mandelbrot" the initialization function is */
/* "initmandelbrot".  The PyMODINIT_FUNC helps with portability */
/* across operating systems and between C and C++ compilers */
PyMODINIT_FUNC
initmandelbrot(void)
{
/* There have been several InitModule functions over time */
Py_InitModule3("mandelbrot", mandelbrot_methods,
mandelbrot__doc__);
}

from distutils.core import setup, Extension

setup(name="mandelbrot", version="0.0",
ext_modules = [Extension("mandelbrot", ["mandelbrot.c"])])

!python setup.py build
running build
running build_ext
building 'mandelbrot' extension
creating build
creating build/temp.linux-x86_64-3.6
/root/.pyenv/versions/miniconda3-4.3.30/envs/caffe2/bin/x86_64-conda_cos6-linux-gnu-cc -DNDEBUG -fwrapv -O2 -Wall -Wstrict-prototypes -march=nocona -mtune=haswell -ftree-vectorize -fPIC -fstack-protector-strong -fno-plt -O2 -pipe -DNDEBUG -D_FORTIFY_SOURCE=2 -O2 -fPIC -I/root/.pyenv/versions/caffe2/include/python3.6m -c mandelbrot.c -o build/temp.linux-x86_64-3.6/mandelbrot.o
mandelbrot.c: In function 'py_iterate_point':
mandelbrot.c:46:9: warning: implicit declaration of function 'PyInt_FromLong'; did you mean 'PyLong_FromLong'? [-Wimplicit-function-declaration]
return PyInt_FromLong((long) iterations);
^~~~~~~~~~~~~~
PyLong_FromLong
mandelbrot.c:46:9: warning: return makes pointer from integer without a cast [-Wint-conversion]
return PyInt_FromLong((long) iterations);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
mandelbrot.c: In function 'initmandelbrot':
mandelbrot.c:68:2: warning: implicit declaration of function 'Py_InitModule3'; did you mean 'Py_Initialize'? [-Wimplicit-function-declaration]
Py_InitModule3("mandelbrot", mandelbrot_methods,
^~~~~~~~~~~~~~
Py_Initialize
mandelbrot.c:70:1: warning: control reaches end of non-void function [-Wreturn-type]
}
^
creating build/lib.linux-x86_64-3.6
x86_64-conda_cos6-linux-gnu-gcc -pthread -shared -Wl,-O2 -Wl,--sort-common -Wl,--as-needed -Wl,-z,relro -Wl,-z,now -Wl,-rpath,/root/.pyenv/versions/miniconda3-4.3.30/envs/caffe2/lib -L/root/.pyenv/versions/miniconda3-4.3.30/envs/caffe2/lib -Wl,-O2 -Wl,--sort-common -Wl,--as-needed -Wl,-z,relro -Wl,-z,now -Wl,-rpath,/root/.pyenv/versions/miniconda3-4.3.30/envs/caffe2/lib -L/root/.pyenv/versions/miniconda3-4.3.30/envs/caffe2/lib -Wl,-O2 -Wl,--sort-common -Wl,--as-needed -Wl,-z,relro -Wl,-z,now -march=nocona -mtune=haswell -ftree-vectorize -fPIC -fstack-protector-strong -fno-plt -O2 -pipe -DNDEBUG -D_FORTIFY_SOURCE=2 -O2 build/temp.linux-x86_64-3.6/mandelbrot.o -o build/lib.linux-x86_64-3.6/mandelbrot.cpython-36m-x86_64-linux-gnu.so
import ctypes
import mandelbrot

ctypes_iterate_point = ctypes.CDLL("mandelbrot.so").iterate_point
ctypes_iterate_point.restype = ctypes.c_int
ctypes_iterate_point.argtypes = [ctypes.c_double, ctypes.c_double, ctypes.c_int]

x = -2
y = -1
w = 2.5
h = 2.0

NY = 40
NX = 70
RANGE_Y = range(NY)
RANGE_X = range(NX)

def render(iterate_point):
chars = []
append = chars.append
for j in RANGE_Y:
for i in RANGE_X:
it = iterate_point(x+w/NX*i, y+h/NY*j, 1000)
if it == 1000:
append("*")
elif it > 5:
append(",")
elif it > 2:
append(".")
else:
append(" ")
append("\n")
return "".join(chars)

import time
t1 = time.time()
s1 = render(mandelbrot.iterate_point)
t2 = time.time()
s2 = render(ctypes_iterate_point)
t3 = time.time()
assert s1 == s2
print (s1)
print ("as C extension", t2-t1)
print ("with ctypes", t3-t2)
---------------------------------------------------------------------------
ImportError                               Traceback (most recent call last)
<ipython-input-19-db7a4ceb6187> in <module>()
1 import ctypes
----> 2 import mandelbrot
3
4 ctypes_iterate_point = ctypes.CDLL("mandelbrot.so").iterate_point
5 ctypes_iterate_point.restype = ctypes.c_int

ImportError: dynamic module does not define module export function (PyInit_mandelbrot)

これはCソースコードがpython2用に書かれているのが原因で、このエラーを解消するにはコードの書き換えが必要になるが、これの習性については後日改めてやるつもりでいる。

スポンサーリンク
スポンサーリンク

フォローする