通过ctypes调用numpy的sse2
简单来说,我想从Python中调用一个共享库,具体来说,是从numpy中调用。这个共享库是用C语言写的,使用了sse2指令。当我开启优化(也就是用 -O2 或 -O1 编译这个库)时,通过ctypes调用共享库时会出现奇怪的段错误(segfault)。但是如果关闭优化(用 -O0),一切都正常,就像直接把库链接到C程序一样(无论是否优化)。下面是我在系统上遇到的情况。开启优化后,gdb报告在 emmintrin.h 的第113行出现了段错误,__P的值被报告为优化掉了。
test.c:
#include <emmintrin.h>
#include <complex.h>
void test(const int m, const double* x, double complex* y) {
int i;
__m128d _f, _x, _b;
double complex f __attribute__( (aligned(16)) );
double complex b __attribute__( (aligned(16)) );
__m128d* _p;
b = 1;
_b = _mm_loadu_pd( (double *) &b );
_p = (__m128d*) y;
for(i=0; i<m; ++i) {
f = cexp(-I*x[i]);
_f = _mm_loadu_pd( (double *) &f );
_x = _mm_loadu_pd( (double *) &x[i] );
_f = _mm_shuffle_pd(_f, _f, 1);
*_p = _mm_add_pd(*_p, _f);
*_p = _mm_add_pd(*_p, _x);
*_p = _mm_mul_pd(*_p,_b);
_p++;
}
return;
}
编译器选项:
gcc -o libtest.so -shared -std=c99 -msse2 -fPIC -O2 -g -lm test.c
test.py:
import numpy as np
import os
def zerovec_aligned(nr, dtype=np.float64, boundary=16):
'''Create an aligned array of zeros.
'''
size = nr * np.dtype(dtype).itemsize
tmp = np.zeros(size + boundary, dtype=np.uint8)
address = tmp.__array_interface__['data'][0]
offset = boundary - address % boundary
return tmp[offset:offset + size].view(dtype=dtype)
lib = np.ctypeslib.load_library('libtest', '.' )
lib.test.restype = None
lib.test.argtypes = [np.ctypeslib.ctypes.c_int,
np.ctypeslib.ndpointer(np.float64, flags=('C', 'A') ),
np.ctypeslib.ndpointer(np.complex128, flags=('C', 'A', 'W') )]
n = 13
y = zerovec_aligned(n, dtype=np.complex128)
x = np.ones(n, dtype=np.float64)
# x = zerovec_aligned(n, dtype=np.float64)
# x[:] = 1.
lib.test(n,x,y)
从C调用test的结果是正常的:
call_from_c.c:
#include <stdio.h>
#include <complex.h>
#include <stdlib.h>
#include <emmintrin.h>
void test(const int m, const double* x, double complex* y);
int main() {
int i;
const int n = 11;
double complex *y = (double complex*) _mm_malloc(n*sizeof(double complex), 16);
double *x = (double *) malloc(n*sizeof(double));
for(i=0; i<n; ++i) {
x[i] = 1;
y[i] = 0;
}
test(n, x, y);
for(i=0; i<n; ++i)
printf("[%f %f]\n", creal(y[i]), cimag(y[i]));
return 1;
}
编译并调用:
gcc -std=c99 -otestc -msse2 -L. -ltest call_from_c.c
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:.
./testc
... 一切正常。
我的系统信息:
- Ubuntu Linux i686 2.6.31-22-generic
- 编译器:gcc (Ubuntu 4.4.1-4ubuntu9)
- Python:Python 2.6.4 (r264:75706, 2009年12月7日) [GCC 4.4.1]
- Numpy:1.4.0
我已经采取了一些措施(参考Python代码),确保y是对齐的,而x的对齐应该没问题(我认为;不过明确对齐x并没有解决问题)。
另外,我在加载b和f时使用了_mm_loadu_pd而不是_mm_load_pd。对于仅C的版本,_mm_load_pd是可以正常工作的(如预期)。但是,当通过ctypes调用这个函数时,使用_mm_load_pd总是会出现段错误(与优化无关)。
我尝试了好几天来解决这个问题,但一直没有成功……我快要忍不住想把显示器砸了。欢迎任何建议。
Daniel
3 个回答
你试过升级到 Numpy 1.5.0b2 吗?只需要运行下面的命令(不过要小心,这可能会导致其他东西出问题,你需要重新编译所有的 pyrex):
sudo easy_install -U numpy
我在使用 H5PY 的时候也遇到过类似的问题(我不得不重新编译 .deb 文件,以便使用最新版本的 numpy),而且在 weave 上也有一些大问题,最新的升级解决了这些问题。
试着用numpy的构建系统来创建你的扩展,这样可以避免可能出现的cflags和ldflags的差异问题。你可以参考这个链接了解更多信息:http://projects.scipy.org/numpy/wiki/NumpySconsExtExamples
我刚刚遇到一个问题,试图从Python调用一些SSE代码。问题似乎在于,GCC编译器希望假设栈是按照16字节对齐的(这是这个架构上最大的原生类型,也就是SSE类型),并且在计算所有偏移量时都基于这个假设。当这个假设不成立时,SSE指令就会出错。
解决这个问题的方法似乎是用
gcc -mstackrealign来编译,这样可以确保函数的开头总是将栈对齐到16字节。