2013-03-22 54 views
10

Tôi đang viết một phần mở rộng C cho chương trình Python của mình cho mục đích tốc độ và chạy vào một số hành vi rất lạ khi cố gắng chuyển vào mảng 3 chiều. Nó hoạt động với một mảng 2 chiều, nhưng tôi chắc chắn rằng tôi đang vặn vẹo thứ gì đó với các con trỏ cố gắng làm cho nó hoạt động với chiều thứ 3. Nhưng đây là một phần kỳ lạ. Nếu tôi chỉ chuyển vào mảng 3-D, lỗi đó sẽ bị lỗi với Lỗi Bus. Nếu (bằng Python) tôi tạo biến của tôi dưới dạng mảng 2D trước, sau đó ghi đè lên nó bằng một mảng 3D, nó hoạt động hoàn hảo. Nếu biến này là mảng trống trước tiên và sau đó là mảng 3D, biến đó sẽ gặp sự cố Seg Fault. Làm thế nào điều đó có thể xảy ra?Vượt qua mảng numpy 3 chiều đến C

Ngoài ra, bất kỳ ai cũng có thể giúp tôi có được mảng 3D hoạt động không? Hoặc tôi có nên bỏ cuộc và vượt qua một mảng 2D và tự định hình lại nó không?

Đây là mã C của tôi:

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]); 
} 

Và đây là mã Python của tôi mà gọi hàm trên:

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) 

Vì vậy, nếu tôi nhận xét ra cả dòng A và B, nó bị treo với một Lỗi bus. Nếu Dòng A ở đó, nhưng Dòng B được nhận xét, nó chạy đúng cách mà không có lỗi. Nếu Dòng B ở đó nhưng Dòng A được nhận xét, nó sẽ in số chính xác nhưng sau đó là lỗi Seg. Cuối cùng, nếu cả hai dòng có mặt, nó cũng in các số chính xác và sau đó là lỗi Seg. Cái quái gì đang diễn ra ở đây vậy?

EDIT: Ok. Wow. Vì vậy, tôi đã sử dụng int bằng Python nhưng gọi chúng là double trong C. Và điều đó hoạt động tốt với mảng 1D và 2D. Nhưng không phải 3D. Vì vậy, tôi đã thay đổi định nghĩa Python của l3 để nổi, và bây giờ tất cả hoạt động tuyệt vời (Cảm ơn bạn rất nhiều Bi Rico).

Nhưng hiện tại, hành vi lạ hơn với Đường A & B! Bây giờ nếu cả hai dòng được nhận xét, chương trình hoạt động. Nếu dòng B là hiện tại nhưng A được nhận xét, nó hoạt động và ditto nếu cả hai đều không được chú ý. Nhưng nếu Line A có mặt và B được nhận xét, tôi sẽ gặp lại lỗi Bus tuyệt vời đó. Tôi thực sự muốn tránh những điều này trong tương lai, do đó, có ai có bất kỳ đầu mối tại sao việc khai báo một biến Python có thể có loại tác động?

CHỈNH SỬA 2: Vâng, tất cả chúng đều là do mảng numpy 3 chiều mà tôi truyền vào. Nếu tôi chỉ truyền trong mảng 1 hoặc 2-D, nó hoạt động như dự kiến ​​và thao tác của các biến Python khác không làm gì cả. Điều này khiến tôi tin rằng vấn đề nằm ở đâu đó trong tính toán tham chiếu của Python. Trong mã C, số tham chiếu được giảm nhiều hơn mức cần thiết cho các mảng 3-D, và khi hàm đó trả về Python cố gắng dọn sạch các đối tượng và cố gắng xóa một con trỏ NULL. Đây chỉ là phỏng đoán của tôi, và tôi đã cố gắng để Py_INCREF(); tất cả mọi thứ tôi có thể nghĩ đến không có kết quả. Tôi đoán tôi sẽ chỉ được sử dụng một mảng 2D và định hình lại nó trong C.

+1

Bạn có chắc chắn rằng '(void **)' là đúng, nên bạn không chỉ cần vượt qua trong một '(void *)'? – seberg

+1

C của tôi hút nhưng ... Không phải là biểu hiện của bạn trong 'if' ngắn mạch nếu cuộc gọi đầu tiên để' PyArray_AsCArray' suceeds? Rất có thể là cuộc gọi thứ hai, tức là cuộc gọi thứ hai cho 'list3', không bao giờ được thực hiện. – Jaime

+0

@seberg Tôi không khẳng định rằng '(void **)' là đúng, nhưng '(void *)' gây ra lỗi Bus. @Jaime Không, hàm đó trả về giá trị âm chỉ khi nó không thành công, rất có thể nếu malloc nó gọi không thành công. – DaveTheScientist

Trả lời

3

Tôi đã đề cập đến điều này trong một bình luận, nhưng tôi hy vọng xả nó ra một chút sẽ giúp làm cho nó rõ ràng hơn.

Khi bạn đang làm việc với mảng có nhiều mảng trong C, bạn nên rõ ràng về cách nhập mảng của mình. Cụ thể, có vẻ như bạn đang khai báo con trỏ của mình là double ***list3, nhưng theo cách bạn đang tạo l3 trong mã python, bạn sẽ nhận được một mảng với dtype npy_intp (Tôi nghĩ). Bạn có thể sửa lỗi này bằng cách sử dụng dtype một cách rõ ràng khi tạo mảng của bạn.

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) 

Lưu ý khác, vì cách thức hoạt động của python gần như không thể thực hiện được mã C như vậy. Tôi biết rằng điều này dường như xung đột với kinh nghiệm thực nghiệm của bạn, nhưng tôi khá chắc chắn về điểm này.

Tôi ít chắc chắn hơn về điều này, nhưng dựa trên kinh nghiệm của tôi với C, lỗi xe buýt và segfaults không xác định. Chúng phụ thuộc vào cấp phát bộ nhớ, căn chỉnh và địa chỉ. Trong một số tình huống mã có vẻ chạy tốt 10 lần, và thất bại trong lần chạy thứ 11 mặc dù không có gì thay đổi.

Bạn đã cân nhắc sử dụng cython? Tôi biết nó không phải là một lựa chọn cho tất cả mọi người, nhưng nếu nó là một lựa chọn bạn có thể nhận được gần như tăng tốc độ C bằng cách sử dụng typed memoryviews.

+0

Lần sau tôi có nhu cầu viết một phần mở rộng C Tôi khá chắc chắn rằng tôi sẽ dành thời gian để học cython. Và có, tất cả mọi thứ tôi biết về Python và C nói rằng không nên có cách nào "Line A và B" có thể có thể tác động đến chương trình C, như mỗi lần L2 được tuyên bố nó nhận được một địa chỉ bộ nhớ mới. Nhưng chúng hoàn toàn dành cho tôi, và đó là một lý do chính khiến tôi bắt đầu câu hỏi này. Tôi có thể dán toàn bộ tập tin nếu ai đó muốn thử trên hệ thống của họ, vì tôi rất muốn nhận được thông tin này. – DaveTheScientist

1

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

Lưu ý Các mô phỏng của một mảng C-phong cách không phải là hoàn chỉnh cho 2-d và 3- d mảng. Ví dụ, các mảng mô phỏng của con trỏ không thể được truyền cho các chương trình con mong đợi các mảng 2-d và 3-d được xác định tĩnh. Để chuyển tới các hàm yêu cầu các kiểu đầu vào đó, bạn phải xác định tĩnh mảng và sao chép dữ liệu cần thiết.

Tôi nghĩ rằng điều này có nghĩa là PyArray_AsCArray trả về một khối bộ nhớ với dữ liệu trong đó theo thứ tự C. Tuy nhiên, để truy cập dữ liệu đó, cần thêm thông tin (xem http://www.phy225.dept.shef.ac.uk/mediawiki/index.php/Arrays,_dynamic_array_allocation). Điều này có thể đạt được bằng cách biết các kích thước trước thời hạn, khai báo một mảng, và sau đó sao chép dữ liệu theo đúng thứ tự. Tuy nhiên, tôi nghi ngờ rằng trường hợp tổng quát hơn là hữu ích hơn: bạn không biết kích thước cho đến khi chúng được trả về. Tôi nghĩ rằng đoạn mã sau sẽ tạo ra khung con trỏ C cần thiết để cho phép dữ liệu được giải quyết.

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); 
} 

tôi không thể tìm thấy một định nghĩa cho npy_intp, các giả định ở trên nó cũng giống như int. Nếu không, bạn cần phải chuyển đổi dim2dim3 thành int mảng trước khi thực hiện mã.

+0

Không chắc chắn về downvoter. Bạn đang đúng về việc chỉ tạo con trỏ, nhưng các cuộc gọi đến PyArray_AsCArray() làm malloc cho tôi. Tôi không tuyệt vời trong C, vì vậy tôi không thực sự biết tại sao tôi cần phải '(void **) & list2', nhưng chương trình bị treo với một lỗi Bus nếu tôi không. – DaveTheScientist

+0

-1: Câu trả lời của bạn không đúng, vì OP không cần cấp phát bộ nhớ cho các mảng. đọc định nghĩa hàm: http://docs.scipy.org/doc/numpy-1.3.x/reference/c-api.array.html#PyArray_AsCArray – meyumer

+0

@meyumer Cảm ơn, tôi đã viết lại hoàn toàn câu trả lời để đối phó với điều này kịch bản, hy vọng bây giờ chính xác. –

4

Thay vì chuyển sang mảng kiểu c, tôi thường truy cập vào các phần tử mảng một cách trực tiếp bằng cách sử dụng PyArray_GETPTR (xem http://docs.scipy.org/doc/numpy/reference/c-api.array.html#data-access). Ví dụ:

Ví dụ: để truy cập một phần tử của mảng ba chiều loại kép sử dụng double elem=*((double *)PyArray_GETPTR3(list3_obj,i,j,k)).

Đối với ứng dụng của bạn, bạn có thể phát hiện đúng số thứ nguyên cho mỗi mảng sử dụng PyArray_NDIM, sau đó truy cập các yếu tố bằng phiên bản thích hợp PyArray_GETPTR.

+0

Tôi muốn chuyển đổi thành một mảng C thông thường vì tôi cho rằng nó sẽ nhanh hơn. Tôi cũng giả định nó sẽ đơn giản hơn, nhưng điều đó rõ ràng là sai ... – DaveTheScientist

+0

Bất kỳ ý tưởng nào nếu điều này chậm hơn hoặc nhanh hơn? –

Các vấn đề liên quan