2016-03-22 15 views
2

Ich schrieb eine Funktion, die numpy Array in C-Code mit CFFI übergibt. Es verwendet das Pufferprotokoll und die Speicheransicht, um die Daten effizient zu übertragen, ohne sie zu kopieren. Dies bedeutet jedoch, dass Sie C-zusammenhängende Arrays übergeben und sicherstellen müssen, dass Sie die richtigen Typen verwenden. Numpy bietet eine Funktion numpy.ascontiguous,, die dies tut. Also iteriere ich über die Argumente und wende diese Funktion an. Die folgende Implementierung funktioniert und kann von allgemeinem Interesse sein. Es ist jedoch langsam, wenn es so oft aufgerufen wird. (Irgendwelche allgemeinen Bemerkungen, wie man es beschleunigt, wäre hilfreich.)Generator Verständnis und Liste Verständnis iterieren anders

Jedoch ist die tatsächliche Frage, wenn Sie das erste Listenverstehen mit einem Generatorverstehen ersetzen, oder wenn Sie den Code so refaktorieren, dass np.ascontigous in genannt wird zweitens zeigen die in den C-Code überführten Zeiger nicht mehr auf den Anfang des numply-Arrays. Ich denke, dass es nicht angerufen wird. Ich wiederhole das Verständnis und verwende nur die Rückgabewerte, warum würde die Verwendung eines Listenverständnisses oder eines Generatorverständnisses irgendetwas verändern?

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

Beachten Sie, dass, obwohl Sie eine Runde des Kopierens vermeiden können, indem Sie auf das Pufferprotokoll angewiesen sind, keinen besonderen Grund zu der Annahme, dass "np.ascontigoarray()" die Daten nicht kopieren würde. Das könnte ein Grund sein, warum der Code langsam ist. Sie können bessere Ergebnisse erzielen, wenn Sie die numpigen Array-Objekte unverändert übergeben und die C-API von Numpy für den Zugriff auf die C-Seite verwenden. Ich denke, das spielt nicht so gut mit cffi, aber es ist etwas zu beachten. –

+0

Ich denke, das ist möglich. Ich denke, obwohl ich denke, könnte falsch sein, dass np.ascontinuous nur kopiert, wenn es muss. Die meiste Zeit sollte es nichts tun. Wenn Sie den Code profilieren, ist der langsame Teil np.issubdtype() und das Casting. Dem Casting kann ich nicht wirklich entkommen. Die Frage kam von der Tatsache, dass ich np.issubdtype() nicht zweimal aufrufen sollte. – sangrey

+0

"Es funktioniert nicht mehr" - können Sie genauer sein? Wie scheitert es? –

Antwort

1

Ich kann nur raten, aber der Fehler von Keep Alive kommen könnte: mit arr_param_buffers eine Liste Verständnis, wie in Ihrem geschrieben Code, dann solange diese lokalen Variablen (dh für die gesamte Dauer des cffi_wrap existieren ()), alle erstellten numpy Arrays sind am Leben. Dadurch können Sie in der nächsten Zeile ffi.from_buffer(memoryview(...)) tun und sicherstellen, dass sie alle Zeiger auf gültige Daten sind.

Wenn Sie arr_param_buffers durch einen Generatorausdruck ersetzen, wird es nacheinander die neuen numpy Arrays generieren, rufen Sie ffi.from_buffer(memoryview(param)) auf ihnen, und dann werfen Sie sie weg. Die ffi.from_buffer(x) gibt ein Objekt zurück, das x am Leben erhalten soll, aber vielleicht x == memoryview(nd) nicht selbst hält das numpy Array nd, für alles, was ich weiß.

+0

Ich denke, das macht Sinn. Vielen Dank. – sangrey