2013-08-27 31 views
51

Các Cython documentation on typed memory views danh sách ba cách gán cho một cái nhìn bộ nhớ gõ:Cách phân bổ bộ nhớ được khuyến nghị cho chế độ xem bộ nhớ đã nhập là gì?

  1. từ một con trỏ C thô,
  2. từ một np.ndarray
  3. từ một cython.view.array.

Giả sử rằng tôi không có dữ liệu được chuyển vào hàm cython từ bên ngoài mà thay vào đó muốn phân bổ bộ nhớ và trả về làm np.ndarray, tôi đã chọn tùy chọn nào? Cũng cho rằng kích thước của bộ đệm đó không phải là một thời gian biên dịch tức là liên tục tôi không thể cấp phát trên stack, nhưng sẽ cần phải malloc cho tùy chọn 1.

3 tùy chọn sẽ do looke một cái gì đó như thế này:

from libc.stdlib cimport malloc, free 
cimport numpy as np 
from cython cimport view 

np.import_array() 

def memview_malloc(int N): 
    cdef int * m = <int *>malloc(N * sizeof(int)) 
    cdef int[::1] b = <int[:N]>m 
    free(<void *>m) 

def memview_ndarray(int N): 
    cdef int[::1] b = np.empty(N, dtype=np.int32) 

def memview_cyarray(int N): 
    cdef int[::1] b = view.array(shape=(N,), itemsize=sizeof(int), format="i") 

Điều đáng ngạc nhiên đối với tôi là trong cả ba trường hợp, Cython generates quite a lot of code để cấp phát bộ nhớ, cụ thể là một cuộc gọi đến __Pyx_PyObject_to_MemoryviewSlice_dc_int. Điều này gợi ý (và tôi có thể sai ở đây, cái nhìn sâu sắc của tôi về các hoạt động bên trong của Cython rất hạn chế) mà trước tiên nó tạo ra một đối tượng Python và sau đó "chuyển" nó vào một khung nhìn bộ nhớ, có vẻ như không cần thiết.

A simple benchmark không tiết lộ nhiều sự khác biệt giữa ba phương pháp, với 2. là nhanh nhất bằng một lề nhỏ.

Nên chọn phương pháp nào trong ba phương pháp này? Hoặc là có một lựa chọn khác, tốt hơn?

Câu hỏi tiếp theo: Tôi muốn trả lại kết quả dưới dạng np.ndarray, sau khi đã làm việc với chế độ xem bộ nhớ đó trong hàm. Là một bộ nhớ đánh máy xem lựa chọn tốt nhất hoặc tôi sẽ chỉ sử dụng giao diện đệm cũ như dưới đây để tạo ra một ndarray ở nơi đầu tiên?

cdef np.ndarray[DTYPE_t, ndim=1] b = np.empty(N, dtype=np.int32) 
+2

Câu hỏi hay, tôi tự hỏi về điều gì đó tương tự. – AlexE

+0

Điểm chuẩn của bạn là câu trả lời hay nhất mà tôi biết. Để trả lời câu hỏi tiếp theo, bạn chỉ có thể khai báo mảng NumPy của bạn theo cách thông thường (bạn thậm chí không cần phải sử dụng giao diện kiểu cũ) và sau đó làm một cái gì đó như 'cdef int [:] arrview = arr' để có được một xem cùng một bộ nhớ được sử dụng cho mảng NumPy. Bạn có thể sử dụng khung nhìn để lập chỉ mục nhanh và chuyển các lát giữa các hàm Cython trong khi vẫn có quyền truy cập vào các hàm NumPy thông qua mảng NumPy. Khi bạn hoàn tất, bạn có thể trả về mảng NumPy. – IanH

+0

có [câu hỏi liên quan tốt ở đây ...] (http://stackoverflow.com/q/18410342/832621) nơi bạn có thể thấy rằng np.empty có thể chậm ... –

Trả lời

56

Look here để tìm câu trả lời.

Ý tưởng cơ bản là bạn muốn cpython.array.arraycpython.array.clone (khôngcython.array.*):

from cpython.array cimport array, clone 

# This type is what you want and can be cast to things of 
# the "double[:]" syntax, so no problems there 
cdef array[double] armv, templatemv 

templatemv = array('d') 

# This is fast 
armv = clone(templatemv, L, False) 

EDIT

Nó chỉ ra rằng các tiêu chuẩn trong chủ đề đó là rác. Dưới đây là bộ của tôi, với timings của tôi:

# cython: language_level=3 
# cython: boundscheck=False 
# cython: wraparound=False 

import time 
import sys 

from cpython.array cimport array, clone 
from cython.view cimport array as cvarray 
from libc.stdlib cimport malloc, free 
import numpy as numpy 
cimport numpy as numpy 

cdef int loops 

def timefunc(name): 
    def timedecorator(f): 
     cdef int L, i 

     print("Running", name) 
     for L in [1, 10, 100, 1000, 10000, 100000, 1000000]: 
      start = time.clock() 
      f(L) 
      end = time.clock() 
      print(format((end-start)/loops * 1e6, "2f"), end=" ") 
      sys.stdout.flush() 

     print("μs") 
    return timedecorator 

print() 
print("INITIALISATIONS") 
loops = 100000 

@timefunc("cpython.array buffer") 
def _(int L): 
    cdef int i 
    cdef array[double] arr, template = array('d') 

    for i in range(loops): 
     arr = clone(template, L, False) 

    # Prevents dead code elimination 
    str(arr[0]) 

@timefunc("cpython.array memoryview") 
def _(int L): 
    cdef int i 
    cdef double[::1] arr 
    cdef array template = array('d') 

    for i in range(loops): 
     arr = clone(template, L, False) 

    # Prevents dead code elimination 
    str(arr[0]) 

@timefunc("cpython.array raw C type") 
def _(int L): 
    cdef int i 
    cdef array arr, template = array('d') 

    for i in range(loops): 
     arr = clone(template, L, False) 

    # Prevents dead code elimination 
    str(arr[0]) 

@timefunc("numpy.empty_like memoryview") 
def _(int L): 
    cdef int i 
    cdef double[::1] arr 
    template = numpy.empty((L,), dtype='double') 

    for i in range(loops): 
     arr = numpy.empty_like(template) 

    # Prevents dead code elimination 
    str(arr[0]) 

@timefunc("malloc") 
def _(int L): 
    cdef int i 
    cdef double* arrptr 

    for i in range(loops): 
     arrptr = <double*> malloc(sizeof(double) * L) 
     free(arrptr) 

    # Prevents dead code elimination 
    str(arrptr[0]) 

@timefunc("malloc memoryview") 
def _(int L): 
    cdef int i 
    cdef double* arrptr 
    cdef double[::1] arr 

    for i in range(loops): 
     arrptr = <double*> malloc(sizeof(double) * L) 
     arr = <double[:L]>arrptr 
     free(arrptr) 

    # Prevents dead code elimination 
    str(arr[0]) 

@timefunc("cvarray memoryview") 
def _(int L): 
    cdef int i 
    cdef double[::1] arr 

    for i in range(loops): 
     arr = cvarray((L,),sizeof(double),'d') 

    # Prevents dead code elimination 
    str(arr[0]) 



print() 
print("ITERATING") 
loops = 1000 

@timefunc("cpython.array buffer") 
def _(int L): 
    cdef int i 
    cdef array[double] arr = clone(array('d'), L, False) 

    cdef double d 
    for i in range(loops): 
     for i in range(L): 
      d = arr[i] 

    # Prevents dead-code elimination 
    str(d) 

@timefunc("cpython.array memoryview") 
def _(int L): 
    cdef int i 
    cdef double[::1] arr = clone(array('d'), L, False) 

    cdef double d 
    for i in range(loops): 
     for i in range(L): 
      d = arr[i] 

    # Prevents dead-code elimination 
    str(d) 

@timefunc("cpython.array raw C type") 
def _(int L): 
    cdef int i 
    cdef array arr = clone(array('d'), L, False) 

    cdef double d 
    for i in range(loops): 
     for i in range(L): 
      d = arr[i] 

    # Prevents dead-code elimination 
    str(d) 

@timefunc("numpy.empty_like memoryview") 
def _(int L): 
    cdef int i 
    cdef double[::1] arr = numpy.empty((L,), dtype='double') 

    cdef double d 
    for i in range(loops): 
     for i in range(L): 
      d = arr[i] 

    # Prevents dead-code elimination 
    str(d) 

@timefunc("malloc") 
def _(int L): 
    cdef int i 
    cdef double* arrptr = <double*> malloc(sizeof(double) * L) 

    cdef double d 
    for i in range(loops): 
     for i in range(L): 
      d = arrptr[i] 

    free(arrptr) 

    # Prevents dead-code elimination 
    str(d) 

@timefunc("malloc memoryview") 
def _(int L): 
    cdef int i 
    cdef double* arrptr = <double*> malloc(sizeof(double) * L) 
    cdef double[::1] arr = <double[:L]>arrptr 

    cdef double d 
    for i in range(loops): 
     for i in range(L): 
      d = arr[i] 

    free(arrptr) 

    # Prevents dead-code elimination 
    str(d) 

@timefunc("cvarray memoryview") 
def _(int L): 
    cdef int i 
    cdef double[::1] arr = cvarray((L,),sizeof(double),'d') 

    cdef double d 
    for i in range(loops): 
     for i in range(L): 
      d = arr[i] 

    # Prevents dead-code elimination 
    str(d) 

Output:

INITIALISATIONS 
Running cpython.array buffer 
0.100040 0.097140 0.133110 0.121820 0.131630 0.108420 0.112160 μs 
Running cpython.array memoryview 
0.339480 0.333240 0.378790 0.445720 0.449800 0.414280 0.414060 μs 
Running cpython.array raw C type 
0.048270 0.049250 0.069770 0.074140 0.076300 0.060980 0.060270 μs 
Running numpy.empty_like memoryview 
1.006200 1.012160 1.128540 1.212350 1.250270 1.235710 1.241050 μs 
Running malloc 
0.021850 0.022430 0.037240 0.046260 0.039570 0.043690 0.030720 μs 
Running malloc memoryview 
1.640200 1.648000 1.681310 1.769610 1.755540 1.804950 1.758150 μs 
Running cvarray memoryview 
1.332330 1.353910 1.358160 1.481150 1.517690 1.485600 1.490790 μs 

ITERATING 
Running cpython.array buffer 
0.010000 0.027000 0.091000 0.669000 6.314000 64.389000 635.171000 μs 
Running cpython.array memoryview 
0.013000 0.015000 0.058000 0.354000 3.186000 33.062000 338.300000 μs 
Running cpython.array raw C type 
0.014000 0.146000 0.979000 9.501000 94.160000 916.073000 9287.079000 μs 
Running numpy.empty_like memoryview 
0.042000 0.020000 0.057000 0.352000 3.193000 34.474000 333.089000 μs 
Running malloc 
0.002000 0.004000 0.064000 0.367000 3.599000 32.712000 323.858000 μs 
Running malloc memoryview 
0.019000 0.032000 0.070000 0.356000 3.194000 32.100000 327.929000 μs 
Running cvarray memoryview 
0.014000 0.026000 0.063000 0.351000 3.209000 32.013000 327.890000 μs 

(. Lý do cho sự "lặp lại" điểm chuẩn là một số phương pháp có những đặc điểm đáng ngạc nhiên khác nhau trong lĩnh vực này)

Để tốc độ khởi tạo:

malloc: Đây là một thế giới khắc nghiệt, nhưng nó rất nhanh.Nếu bạn cần phân bổ nhiều thứ và có hiệu suất lập lại và lập chỉ mục không bị cản trở, điều này phải là nó. Nhưng thông thường, bạn là một cá cược tốt cho ...

cpython.array raw C type: Thật đáng buồn, nó rất nhanh. Và nó an toàn. Thật không may nó đi qua Python để truy cập vào các trường dữ liệu của nó. Bạn có thể tránh điều đó bằng cách sử dụng một mẹo tuyệt vời:

arr.data.as_doubles[i] 

mang đến tốc độ tiêu chuẩn trong khi tháo an toàn! Điều này làm cho việc thay thế tuyệt vời này là cho malloc, về cơ bản là phiên bản được tính tham khảo khá!

cpython.array buffer: Chỉ đến ba lần bốn lần thời gian thiết lập là malloc, đây có vẻ là một cược tuyệt vời. Thật không may nó có chi phí đáng kể (mặc dù nhỏ so với các chỉ thị boundscheckwraparound). Điều đó có nghĩa là nó chỉ thực sự cạnh tranh với các biến thể an toàn đầy đủ, nhưng nó là là nhanh nhất trong số đó để khởi tạo. Lựa chọn của bạn.

cpython.array memoryview: Đây là mức độ chậm hơn malloc để khởi tạo. Đó là một sự xấu hổ, nhưng nó lặp lại nhanh như vậy. Đây là giải pháp tiêu chuẩn mà tôi sẽ đề xuất trừ khi boundscheck hoặc wraparound được bật (trong trường hợp này cpython.array buffer có thể là một sự cân bằng hấp dẫn hơn).

Phần còn lại. Người duy nhất đáng giá bất cứ thứ gì là numpy, do nhiều phương pháp thú vị gắn liền với các vật thể. Đó là nó, mặc dù.

+0

Cảm ơn bạn đã khảo sát và ủng hộ toàn diện nó lên với con số! – kynan

+1

Câu trả lời hay! Tôi có nghĩ rằng chỉ có giải pháp 'malloc' thuần túy sẽ phá vỡ hoàn toàn sự cần thiết để có được GIL? Tôi quan tâm đến cách phân bổ mảng đa chiều trong các chuỗi công nhân song song. –

+0

Hãy thử và báo cáo lại! – Veedrac

8

Theo dõi câu trả lời của Veedrac: hãy lưu ý rằng việc sử dụng hỗ trợ memoryview của cpython.array với python 2.7 có vẻ như dẫn đến rò rỉ bộ nhớ hiện tại. Điều này có vẻ là một vấn đề tồn tại lâu dài vì nó được đề cập trong danh sách gửi thư của người dùng cython here trong một bài đăng từ tháng 11 năm 2012. Chạy tập lệnh chuẩn của Veedrac với phiên bản Cython 0.22 với cả Python 2.7.6 và Python 2.7.9 dẫn đến rò rỉ bộ nhớ lớn khi khởi tạo cpython.array bằng giao diện buffer hoặc memoryview. Không có rò rỉ bộ nhớ xảy ra khi chạy kịch bản với Python 3.4. Tôi đã gửi một báo cáo lỗi về điều này cho danh sách gửi thư của nhà phát triển Cython.

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