2013-03-22 5 views
10
übergeben

Ich schreibe eine C-Erweiterung zu meinem Python-Programm für Geschwindigkeitszwecke, und renne in ein sehr merkwürdiges Verhalten, indem ich versuche, ein 3-dimensionales numpiges Array zu übergeben. Es funktioniert mit einem 2-dimensionalen Array, aber ich bin mir sicher, dass ich etwas mit den Zeigern vermassle, die versuchen, es mit der 3. Dimension zu arbeiten. Aber hier ist der komische Teil. Wenn ich nur ein 3-D-Array übergebe, stürzt es mit einem Busfehler ab. Wenn ich (in Python) meine Variable zuerst als ein 2D-Array erzeuge und sie dann mit einem 3D-Array überschreibe, funktioniert es einwandfrei . Wenn die Variable zuerst ein leeres Array und dann ein 3D-Array ist, stürzt sie mit einem Seg Fehler ab. Wie kann das passieren?3-dimensionales numpy Array an C

Kann mir auch jemand helfen, ein 3D-Array zu bekommen? Oder sollte ich einfach aufgeben und ein 2D-Array übergeben und es selbst umformen?

Hier ist mein C-Code:

static PyObject* func(PyObject* self, PyObject* args) { 
    PyObject *list2_obj; 
    PyObject *list3_obj; 
    if (!PyArg_ParseTuple(args, "OO", &list2_obj, &list3_obj)) 
    return NULL; 

    double **list2; 
    double ***list3; 

    //Create C arrays from numpy objects: 
    int typenum = NPY_DOUBLE; 
    PyArray_Descr *descr; 
    descr = PyArray_DescrFromType(typenum); 
    npy_intp dims[3]; 
    if (PyArray_AsCArray(&list2_obj, (void **)&list2, dims, 2, descr) < 0 || PyArray_AsCArray(&list3_obj, (void ***)&list3, dims, 3, descr) < 0) { 
    PyErr_SetString(PyExc_TypeError, "error converting to c array"); 
    return NULL; 
    } 
    printf("2D: %f, 3D: %f.\n", list2[3][1], list3[1][0][2]); 
} 

Und hier ist mein Python-Code, der die obige Funktion aufruft:

import cmod, numpy 
l2 = numpy.array([[1.0,2.0,3.0], [4.0,5.0,6.0], [7.0,8.0,9.0], [3.0, 5.0, 0.0]]) 

l3 = numpy.array([[2,7, 1], [6, 3, 9], [1, 10, 13], [4, 2, 6]]) # Line A 
l3 = numpy.array([])            # Line B 

l3 = numpy.array([[[2,7, 1, 11], [6, 3, 9, 12]], 
       [[1, 10, 13, 15], [4, 2, 6, 2]]]) 

cmod.func(l2, l3) 

Also, wenn ich beide Linie Kommentar aus A und B, es mit einem Absturz Busfehler Wenn Zeile A vorhanden ist, Zeile B jedoch auskommentiert ist, wird sie fehlerfrei ausgeführt. Wenn Zeile B vorhanden ist, aber Zeile A auskommentiert ist, werden die richtigen Zahlen und dann die Seg-Fehler ausgegeben. Wenn beide Zeilen vorhanden sind, werden auch die richtigen Nummern und dann Seg-Fehler ausgegeben. Was zum Teufel geht hier vor?

EDIT: Ok. Beeindruckend. Also benutzte ich int in Python, aber nannte sie double in C. Und das funktionierte gut mit 1D- und 2D-Arrays. Aber nicht 3D. Also habe ich die Python-Definition von l3 geändert, um Floats zu sein, und jetzt funktioniert alles fantastisch (Vielen Dank Bi Rico).

Aber jetzt, seltsamer Verhalten mit Linien A & B! Wenn nun beide Zeilen auskommentiert sind, funktioniert das Programm. Wenn Zeile B vorhanden ist, aber A auskommentiert ist, funktioniert es, und dito, wenn beide unkommentiert sind. Aber wenn Zeile A vorhanden ist und B auskommentiert ist, bekomme ich wieder diesen fantastischen Busfehler. Ich möchte diese in Zukunft wirklich vermeiden, also hat irgendjemand irgendeine Ahnung, warum die Deklaration einer Python-Variablen diese Art von Auswirkungen haben kann?

EDIT 2: Nun, so verrückt, wie diese Fehler sind, sind sie alle aufgrund der 3-dimensionalen numpy Array, die ich weitergeben. Wenn ich nur in 1- oder 2-D-Arrays übergeben, verhält es sich wie erwartet und Manipulation der anderen Python-Variablen tut nichts. Das führt mich zu der Annahme, dass das Problem irgendwo in Pythons Referenzzählung liegt. Im C-Code wird die Referenzzahl mehr als für die 3-D-Arrays verringert, und wenn diese Funktion zurückkehrt, versucht Python, Objekte zu bereinigen und versucht, einen NULL-Zeiger zu löschen. Das ist nur meine Vermutung, und ich habe versucht, Py_INCREF(); alles, was ich denken konnte, ohne Erfolg. Ich denke, ich werde nur einen 2D-Array verwenden und Umformen es in C.

+1

Sind Sie sicher, dass '(void **)' richtig ist, sollte nicht nur Sie passieren in a '(void *)'? – seberg

+1

Mein C saugt aber ... Ist Ihr Ausdruck im 'if' nicht kurzgeschlossen, wenn der erste Aufruf von' PyArray_AsCArray' erfolgreich ist? Es kann sehr gut sein, dass der zweite Aufruf, d. H. Der für "list3", niemals gemacht wird. – Jaime

+0

@seberg Ich bin nicht sicher, dass '(void **)' korrekt ist, aber '(void *)' verursacht einen Busfehler. @Jaime Nein, diese Funktion gibt nur dann negative Werte zurück, wenn sie fehlschlägt, höchstwahrscheinlich, wenn das von ihr aufgerufene malloc fehlschlägt. – DaveTheScientist

Antwort

3

Ich erwähnte das bereits in einem Kommentar, aber ich hoffe, es ein wenig zu spülen hilft, es klarer zu machen.

Wenn Sie mit numpy Arrays in C arbeiten, ist es gut, sich explizit mit der Eingabe Ihrer Arrays zu befassen. Insbesondere sieht es so aus, als würden Sie Ihre Zeiger als double ***list3 deklarieren, aber so wie Sie in Ihrem Python-Code erstellen, erhalten Sie ein Array mit dtype npy_intp (glaube ich). Sie können dies beheben, indem Sie beim Erstellen Ihrer Arrays explizit den dtype verwenden.

import cmod, numpy 
l2 = numpy.array([[1.0,2.0,3.0], 
        [4.0,5.0,6.0], 
        [7.0,8.0,9.0], 
        [3.0, 5.0, 0.0]], dtype="double") 

l3 = numpy.array([[[2,7, 1, 11], [6, 3, 9, 12]], 
        [[1, 10, 13, 15], [4, 2, 6, 2]]], dtype="double") 

cmod.func(l2, l3) 

Noch ein Hinweis, wegen der Art und Weise Python auf dem C-Code um einen Effekt zu haben, was auch immer es ist fast unmöglich für „Linie A“ und „Linie B“ arbeitet. Ich weiß, dass dies mit Ihrer empirischen Erfahrung zu kollidieren scheint, aber ich bin mir ziemlich sicher in diesem Punkt.

Ich bin ein bisschen weniger sicher, aber basierend auf meiner Erfahrung mit C, Bus-Fehler und segfaults sind nicht deterministisch. Sie hängen von Speicherzuordnung, Ausrichtung und Adressen ab. In einigen Situationen scheint der Code zehn Mal fehlerfrei zu sein und schlägt beim 11. Lauf fehl, obwohl sich nichts geändert hat.

Haben Sie in Betracht gezogen, cython zu verwenden? Ich weiß, es ist keine Option für alle, aber wenn es eine Option ist, könnten Sie fast C-Level-Beschleunigungen mit typed memoryviews bekommen.

+0

Das nächste Mal, wenn ich eine C-Erweiterung schreiben muss, bin ich ziemlich sicher, dass ich die Zeit verbringen werde, Cython zu lernen. Und ja, alles, was ich über Python und C weiß, besagt, dass es keine Möglichkeit geben sollte, dass "Zeile A und B" möglicherweise das C-Programm beeinflussen könnte, da jedes Mal, wenn L2 deklariert wird, eine neue Speicheradresse erhalten wird. Aber sie sind absolut für mich, und das ist ein wichtiger Grund, warum ich diese Frage gestellt habe. Ich könnte die ganzen Dateien einfügen, wenn jemand anderes ihr System anprobieren möchte, da ich gerne auf den Grund gehen würde. – DaveTheScientist

1

Nach http://docs.scipy.org/doc/numpy/reference/c-api.array.html?highlight=pyarray_ascarray#PyArray_AsCArray:

Hinweis Die Simulation eines Arrays C-Stil nicht vollständig für 2-d und 3- d Arrays. Zum Beispiel können die simulierten Arrays von Zeigern nicht an Subroutinen übergeben werden, die bestimmte, statisch definierte 2-d- und 3-d-Arrays erwarten. Um zu Funktionen zu gelangen, die diese Art von Eingängen erfordern, müssen Sie das erforderliche Array statisch definieren und Daten kopieren.

Ich denke, dass dies bedeutet, dass PyArray_AsCArray einen Speicherblock mit den Daten in C-Reihenfolge zurückgibt. Für den Zugriff auf diese Daten sind jedoch weitere Informationen erforderlich (siehe http://www.phy225.dept.shef.ac.uk/mediawiki/index.php/Arrays,_dynamic_array_allocation). Dies kann entweder dadurch erreicht werden, dass die Dimensionen im Voraus bekannt sind, ein Array deklariert wird und dann die Daten in der richtigen Reihenfolge kopiert werden. Ich vermute jedoch, dass ein allgemeinerer Fall nützlicher ist: Sie kennen die Dimensionen erst, wenn sie zurückgegeben werden. Ich denke, dass der folgende Code das notwendige C-Pointer-Framework erstellen wird, um die Adressierung der Daten zu ermöglichen.

static PyObject* func(PyObject* self, PyObject* args) { 
    PyObject *list2_obj; 
    PyObject *list3_obj; 
    if (!PyArg_ParseTuple(args, "OO", &list2_obj, &list3_obj)) return NULL; 

    double **list2; 
    double ***list3; 

    // For the final version 
    double **final_array2; 
    double **final_array2; 

    // For loops 
    int i,j; 

    //Create C arrays from numpy objects: 
    int typenum = NPY_DOUBLE; 
    PyArray_Descr *descr; 
    descr = PyArray_DescrFromType(typenum); 

    // One per array coming back ... 
    npy_intp dims2[2]; 
    npy_intp dims3[3]; 

    if (PyArray_AsCArray(&list2_obj, (void **)&list2, dims2, 2, descr) < 0 || PyArray_AsCArray(&list3_obj, (void ***)&list3, dims3, 3, descr) < 0) { 
     PyErr_SetString(PyExc_TypeError, "error converting to c array"); 
     return NULL; 
    } 

    // Create the pointer arrays needed to access the data 

    // 2D array 
    final_array2 = calloc(dim2[0], sizeof(double *)); 
    for (i=0; i<dim[0]; i++) final_array2[i] = list2 + dim2[1]*sizeof(double); 

    // 2D array 
    final_array3 = calloc(dim3[0], sizeof(double **)); 
    final_array3[0] = calloc(dim3[0]*dim3[1], sizeof(double *)); 
    for (i=0; i<dim[0]; i++) { 
     final_array3[i] = list2 + dim3[1]*sizeof(double *); 
     for (j=0; j<dim[1]; j++) { 
      final_array[i][j] = final_array[i] + dim3[2]*sizeof(double); 
     } 
    } 

    printf("2D: %f, 3D: %f.\n", final_array2[3][1], final_array3[1][0][2]); 
    // Do stuff with the arrays 

    // When ready to complete, free the array access stuff 
    free(final_array2); 

    free(final_array3[0]); 
    free(final_array3); 

    // I would guess you also need to free the stuff allocated by PyArray_AsCArray, if so: 
    free(list2); 
    free(list3); 
} 

konnte ich nicht eine Definition für npy_intp, finden die oben nimmt die gleiche wie int ist. Wenn dies nicht der Fall ist, müssen Sie dim2 und dim3 in int Arrays konvertieren, bevor Sie den Code ausführen.

+0

Nicht sicher über den Downvoter. Sie haben recht, wenn Sie nur den Zeiger erstellen, aber die Aufrufe von PyArray_AsCArray() führen den malloc für mich aus. Ich bin nicht großartig in C, also weiß ich nicht wirklich, warum ich '(void **) & list2' brauchen muss, aber das Programm stürzt mit einem Bus-Fehler ab, wenn ich es nicht tue. – DaveTheScientist

+0

-1: Ihre Antwort ist falsch, weil das OP keinen Speicher für die Arrays reservieren muss. Lesen Sie die Funktionsdefinition: http://docs.scipy.org/doc/numpy-1.3.x/reference/c-api.array.html#PyArray_AsCArray – meyumer

+0

@meyumer Danke, ich habe die Antwort komplett umgeschrieben, um damit fertig zu werden Szenario, hoffentlich jetzt richtig. –

4

Anstatt in ein c-artiges Array zu konvertieren, greife ich normalerweise direkt auf numpy Array-Elemente unter Verwendung PyArray_GETPTR (siehe http://docs.scipy.org/doc/numpy/reference/c-api.array.html#data-access).

Um beispielsweise auf ein Element eines 3-dimensionalen numpy-Arrays vom Typ double use double elem=*((double *)PyArray_GETPTR3(list3_obj,i,j,k)) zuzugreifen.

Für Ihre Anwendung können Sie die korrekte Anzahl der Dimensionen für jedes Array unter Verwendung von PyArray_NDIM ermitteln und dann auf Elemente mit der entsprechenden Version PyArray_GETPTR zugreifen.

+0

Ich wollte in ein reguläres C-Array konvertieren, weil ich annahm, dass es schneller wäre. Ich nahm auch an, es wäre einfacher, aber das war eindeutig falsch ... – DaveTheScientist

+0

Eine Idee, ob das langsamer oder schneller ist? –