2013-03-04 56 views
8

我最近試過PyPy,並被這種方法所吸引。我有很多用於Python的C擴展,它們都使用PyArray_DATA()來獲取指向numpy數組的數據部分的指針。不幸的是,PyPy似乎並沒有在其cpyext模塊中爲其numpypy陣列等效導出,所以我嘗試按照其網站上的建議使用​​。這將推動獲取指向Python級別的任務。便攜式/快速獲取指向Numpy/Numpypy數據的指針

似乎有兩種方式:

import ctypes as C 
p_t = C.POINTER(C.c_double) 

def get_ptr_ctypes(x): 
    return x.ctypes.data_as(p_t) 

def get_ptr_array(x): 
    return C.cast(x.__array_interface__['data'][0], p_t) 

只有第二個工作在PyPy,所以兼容性的選擇是明確的。對於CPython來說,兩者都很慢,對我的應用程序來說是一個完整的瓶頸!有沒有一種快速便攜的方式來獲得這個指針?或者PyPy(可能沒有文檔)有相當於PyArray_DATA()

回答

4

我還沒有找到一個完全令人滿意的解決辦法,但仍然有一些東西我們可以通過在CPython中獲得更少的開銷來獲得指針。首先,上述兩種方式如此之慢的原因是.ctypes.__array_interface__都是按需屬性,其由array_ctypes_get()array_interface_get()numpy/numpy/core/src/multiarray/getset.c中設置。第一個導入ctypes並創建一個numpy.core._internal._ctypes實例,而第二個創建一個新的字典,並在數據指針之外添加大量不必要的東西。

沒有什麼人能在這個開銷Python層面做,但可以寫在C級的微型模塊繞過大部分的開銷:

#include <Python.h> 
#include <numpy/arrayobject.h> 

PyObject *_get_ptr(PyObject *self, PyObject *obj) { 
    return PyLong_FromVoidPtr(PyArray_DATA(obj)); 
} 

static PyMethodDef methods[] = { 
    {"_get_ptr", _get_ptr, METH_O, "Wrapper to PyArray_DATA()"}, 
    {NULL, NULL, 0, NULL} 
}; 

PyMODINIT_FUNC initaccel(void) { 
    Py_InitModule("accel", methods); 
} 

正常編譯就可以作爲擴展在setup.py,進口爲

try: 
    from accel import _get_ptr 
    def get_ptr(x): 
     return C.cast(_get_ptr(x), p_t) 
except ImportError: 
    get_ptr = get_ptr_array 

在PyPy,from accel import _get_ptr將失敗,get_ptr將回落至get_ptr_array,這與Numpypy工作。

就性能而言,對於輕量級的C函數調用,ctypes + accel._get_ptr()仍然比本地CPython擴展慢很多,後者基本沒有開銷。它當然比上面的get_ptr_ctypes()get_ptr_array()快得多,因此對於中等重量的C函數調用來說開銷可能變得不重要。

一個已經獲得了兼容PyPy,但我不得不說,花費相當多的時間試圖評估PyPy我的科學計算應用程序後,我沒有看到它的未來,只要他們(相當固執地)拒絕支持完整的CPython API。

更新

我發現,現在ctypes.cast()變得引進accel._get_ptr()後的瓶頸。通過將接口中的所有指針聲明爲ctypes.c_void_p,可以擺脫這些強制轉換。這是我結束了:

def get_ptr_ctypes2(x): 
    return x.ctypes._data 

def get_ptr_array(x): 
    return x.__array_interface__['data'][0] 

try: 
    from accel import _get_ptr as get_ptr 
except ImportError: 
    get_ptr = get_ptr_array 

這裏,get_ptr_ctypes2()通過直接訪問隱藏ndarray.ctypes._data屬性避免了演員。下面是用於調用重的重量和重量輕的C函數在Python一些時序結果:

       heavy C (few calls)  light C (many calls) 
ctypes + get_ptr_ctypes():   0.71 s     15.40 s 
ctypes + get_ptr_ctypes2():  0.68 s     13.30 s 
ctypes + get_ptr_array():   0.65 s     11.50 s 
ctypes + accel._get_ptr():   0.63 s     9.47 s 

native CPython:     0.62 s     8.54 s 
Cython (no decorators):   0.64 s     9.96 s 

所以,用accel._get_ptr()和無ctypes.cast() S,ctypes的是速度與天然CPython的延伸實際上具有競爭力。所以,我只是要等到有人改寫h5pymatplotlibscipy與ctypes的是能夠嘗試PyPy對於事情的嚴重性...

0

這可能沒有足夠的答案,但希望有一個很好的提示。我在我的代碼的某些部分使用scipy.weave.inline()。我不太瞭解接口本身的速度,因爲我執行的函數很重,只依賴於幾個指針/數組,但對我來說似乎很快。也許你可以從scipy.weave代碼一些啓發,特別是attempt_function_call

https://github.com/scipy/scipy/blob/master/scipy/weave/inline_tools.py#L390

如果你想看看由scipy.weave生成的C++代碼,

  1. 產生從這裏一個簡單的例子:http://docs.scipy.org/doc/scipy/reference/tutorial/weave.html

  2. 運行的python腳本

  3. 得到scipy.weave緩存文件夾:

    import scipy.weave.catalog as ctl 
    ctl.default_dir() 
    Out[5]: '/home/user/.python27_compiled' 
    
  4. 有文件夾中看看生成的C++代碼
+0

不幸的是,'scipy.weave'確實沒有什麼比產生使用CPython的API的C代碼其他( '#include '),這不會與'PyPy'一起使用。在CPython API中,'PyArray_DATA()'是獲得指向numpy數組數據部分的指針的最有效方法,但它不可移植到PyPy。 – Stefan 2013-03-28 10:19:32