2016-03-22 11 views
2

CFFIを使用してnumpy配列をCコードに渡す関数を記述しました。これは、バッファプロトコルとメモリビューを使用して、データをコピーせずに効率的に渡します。しかし、これは、C連続配列を渡し、正しい型を使用することを保証する必要があることを意味します。 Numpyはこれを行う関数numpy.ascontiguous,を提供します。だから私は議論を繰り返し、この関数を適用します。以下の実装は機能し、一般的な関心事である可能性があります。しかし、それが呼び出される回数があれば、それは遅いです。 (それをスピードアップする方法に関する一般的なコメントは役に立ちます)ジェネレーターの理解とリストの理解は異なる方法で反復する

しかし、実際の質問は、最初のリストの理解をジェネレータの理解に置き換えた場合、またはコードをリファクタリングしてnp.ascontigousがもう1つは、Cコードに渡されたポインタがnumpy配列の先頭を指していないことです。私はそれが呼ばれていないと思う。私は理解を繰り返し、戻り値のみを使用しています。なぜリストの理解やジェネレーターの理解が何を変えるのでしょうか?

def cffi_wrap(cffi_func, ndarray_params, pod_params, return_shapes=None): 
    """ 
    Wraps a cffi function to allow it to be called on numpy arrays. 

    It uss the numpy buffer protocol and and the cffi buffer protocol to pass the 
    numpy array into the c function without copying any of the parameters. 
    You will need to pass dimensions into the C function, which you can do using 
    the pod_params. 

    Parameters 
    ---------- 
    cffi_func : c function 
     This is a c function declared using cffi. It must take double pointers and 
     plain old data types. The arguments must be in the form of numpy arrays, 
     plain old data types, and then the returned numpy arrays. 
    ndarray_params : iterable of ndarrays 
     The numpy arrays to pass into the function. 
    pod_params : tuple of plain old data 
     This plain old data objects to pass in. This may include for example 
     dimensions. 
    return_shapes : iterable of tuples of positive ints 
      The shapes of the returned objects. 

    Returns 
    ------- 
    return_vals : ndarrays of doubles. 
     The objects to be calculated by the cffi_func. 

    """ 

    arr_param_buffers = [np.ascontiguousarray(param, np.float64) 
     if np.issubdtype(param.dtype, np.float) 
     else np.ascontiguousarray(param, np.intc) for param in ndarray_params] 
    arr_param_ptrs = [ffi.cast("double *", ffi.from_buffer(memoryview(param))) 
     if np.issubdtype(param.dtype, np.float) 
     else ffi.cast("int *", ffi.from_buffer(memoryview(param))) 
     for param in arr_param_buffers] 

    if return_shapes is not None: 

     return_vals_ptrs = tuple(ffi.new("double[" + str(np.prod(shape)) + "]") 
      for shape in return_shapes) 
     returned_val = cffi_func(*arr_param_ptrs, *pod_params, *return_vals_ptrs) 
     return_vals = tuple(np.frombuffer(ffi.buffer(
       return_val))[:np.prod(shape)].reshape(shape) 
       for shape, return_val in zip(return_shapes, return_vals_ptrs)) 
    else: 
     returned_val = cffi_func(*arr_param_ptrs, *pod_params) 
     return_vals = None 

    if returned_val is not None and return_vals is not None: 
     return_vals = return_vals + (returned_val,) 
    elif return_vals is None: 
     return_vals = (returned_val,) 

    if len(return_vals) == 1: 
     return return_vals[0] 
    else: 
     return return_vals 
+0

バッファプロトコルに依存して1回のコピーを避けることはできますが、 'np.ascontiguousarray()'がデータをコピーしないと考える特別な理由はありません。それがコードが遅い理由の1つになる可能性があります。修飾されていない配列オブジェクトをそのまま渡し、NumpyのC APIを使ってC側でアクセスすると、より良い結果が得られるかもしれません。私はcffiでうまくいきませんが、それは何かを考慮する必要があります。 –

+0

私はそれが可能だと思います。私は間違っていると思いますが、np.ascontiguousはコピーが必要な場合にのみコピーすると思います。ほとんどの場合、何もするべきではありません。コードをプロファイルする場合、遅い部分はnp.issubdtype()とキャストです。キャスティング、私は本当に離れて得ることはできません。問題は、np.issubdtype()を2回呼び出す必要がないという事実から来ています。 – sangrey

+0

"それは動作を停止" ---より正確にすることはできますか?どのように失敗するのですか? –

答えて

1

私は推測しているが、エラーがキープアライブから来る可能性:arr_param_buffersとリスト内包を、あなたの投稿のコードのように、そして限り、このローカル変数が存在する(すなわちcffi_wrapの全期間( ))、作成されたすべての配列は生きています。これにより、次の行でffi.from_buffer(memoryview(...))を実行し、それらがすべて有効なデータへのポインタであることを確認できます。

arr_param_buffersをジェネレータ式に置き換えると、numpyの新しい配列が1つずつ生成され、ffi.from_buffer(memoryview(param))が呼び出され、それらを放棄します。 ffi.from_buffer(x)は、xを維持するオブジェクトを返しますが、おそらくx == memoryview(nd)はそれ自身がnumpyの配列ndを生かしているとは限りません。

+0

私はそれが理にかなっていると思います。ありがとうございました。 – sangrey

関連する問題