2015-10-11 12 views
5

Tôi đã viết hàm Python tính các tương tác điện từ theo cặp giữa một số lượng lớn (N ~ 10^3) hạt và lưu trữ kết quả trong một NxN complex128 ndarray. Nó chạy, nhưng nó là phần chậm nhất của một chương trình lớn hơn, mất khoảng 40 giây khi N = 900 [sửa]. Mã gốc trông như thế này:Tốc độ Cython không lớn như mong đợi

import numpy as np 
def interaction(s,alpha,kprop): # s is an Nx3 real array 
           # alpha is complex 
           # kprop is float 

    ndipoles = s.shape[0] 

    Amat = np.zeros((ndipoles,3, ndipoles, 3), dtype=np.complex128) 
    I = np.array([[1,0,0],[0,1,0],[0,0,1]]) 
    im = complex(0,1) 

    k2 = kprop*kprop 

    for i in range(ndipoles): 
     xi = s[i,:] 
     for j in range(ndipoles): 
      if i != j: 
       xj = s[j,:] 
       dx = xi-xj 
       R = np.sqrt(dx.dot(dx)) 
       n = dx/R 
       kR = kprop*R 
       kR2 = kR*kR 
       A = ((1./kR2) - im/kR) 
       nxn = np.outer(n, n) 
       nxn = (3*A-1)*nxn + (1-A)*I 
       nxn *= -alpha*(k2*np.exp(im*kR))/R 
      else: 
       nxn = I 

      Amat[i,:,j,:] = nxn 

    return(Amat.reshape((3*ndipoles,3*ndipoles))) 

tôi chưa bao giờ sử dụng trước đó Cython, nhưng điều đó dường như là một nơi tốt để bắt đầu trong nỗ lực của tôi để tăng tốc, vì vậy tôi khá nhiều một cách mù quáng thích nghi các kỹ thuật tôi tìm thấy trong trực tuyến hướng dẫn. Tôi đã có một số tăng tốc (30 giây so với 40 giây), nhưng không gần như là kịch tính như tôi mong đợi, vì vậy tôi tự hỏi liệu tôi đang làm điều gì đó sai hoặc đang thiếu một bước quan trọng. Sau đây là nỗ lực hết sức mình tại cythonizing thói quen trên:

import numpy as np 
cimport numpy as np 

DTYPE = np.complex128 
ctypedef np.complex128_t DTYPE_t 

def interaction(np.ndarray s, DTYPE_t alpha, float kprop): 

    cdef float k2 = kprop*kprop 
    cdef int i,j 
    cdef np.ndarray xi, xj, dx, n, nxn 
    cdef float R, kR, kR2 
    cdef DTYPE_t A 

    cdef int ndipoles = s.shape[0] 
    cdef np.ndarray Amat = np.zeros((ndipoles,3, ndipoles, 3), dtype=DTYPE) 
    cdef np.ndarray I = np.array([[1,0,0],[0,1,0],[0,0,1]]) 
    cdef DTYPE_t im = complex(0,1) 

    for i in range(ndipoles): 
     xi = s[i,:] 
     for j in range(ndipoles): 
      if i != j: 
       xj = s[j,:] 
       dx = xi-xj 
       R = np.sqrt(dx.dot(dx)) 
       n = dx/R 
       kR = kprop*R 
       kR2 = kR*kR 
       A = ((1./kR2) - im/kR) 
       nxn = np.outer(n, n) 
       nxn = (3*A-1)*nxn + (1-A)*I 
       nxn *= -alpha*(k2*np.exp(im*kR))/R 
      else: 
       nxn = I 

      Amat[i,:,j,:] = nxn 

    return(Amat.reshape((3*ndipoles,3*ndipoles))) 
+6

Numpy là thư viện C. Và sử dụng BLAS để làm đại số, vì vậy nó khá nhanh. Tôi không thực sự hiểu làm thế nào cython internals hoạt động, nhưng được numpy đã C mã, đạt được trong tốc độ là trong bất cứ điều gì "không gumpy". –

+0

Tôi giả định rằng đủ các hoạt động theo từng dòng trong vòng lặp lồng nhau yêu cầu lời gọi trực tiếp của trình thông dịch Python và do đó các dòng đó có thể là chi phí chủ đạo liên quan đến Numpy - nhưng có lẽ không? –

+1

Bạn có thể thử nhập các mảng cứng nhắc của mình để trình biên dịch biết các loại bên trong mảng. Tuy nhiên, không chắc chắn sự khác biệt lớn như thế nào. Bạn có thể muốn chạy một hồ sơ trên mã python để xem nơi bạn đang thực sự mất tốc độ. Nếu phần lớn thời gian được chi tiêu trong các thói quen sần sùi, bạn sẽ không thu được nhiều tiền bằng cách sử dụng cython. – cel

Trả lời

11

Sức mạnh thực sự của NumPy là trong việc thực hiện một hoạt động trên một số lượng lớn các yếu tố một cách vectorized thay vì sử dụng hoạt động mà trong khối lây lan qua vòng. Trong trường hợp của bạn, bạn đang sử dụng hai vòng lặp lồng nhau và một câu lệnh IF có điều kiện. Tôi sẽ đề xuất mở rộng kích thước của các mảng trung gian, sẽ mang lại cho NumPy's powerful broadcasting capability để đi vào chơi và do đó các hoạt động tương tự có thể được sử dụng trên tất cả các yếu tố trong một lần thay vì các khối dữ liệu nhỏ trong vòng lặp.

Để mở rộng thứ nguyên, None/np.newaxis có thể được sử dụng. Vì vậy, việc thực hiện vectorized theo một tiền đề như vậy sẽ giống như thế này -

def vectorized_interaction(s,alpha,kprop): 

    im = complex(0,1) 
    I = np.array([[1,0,0],[0,1,0],[0,0,1]]) 
    k2 = kprop*kprop 

    # Vectorized calculations for dx, R, n, kR, A 
    sd = s[:,None] - s 
    Rv = np.sqrt((sd**2).sum(2)) 
    nv = sd/Rv[:,:,None] 
    kRv = Rv*kprop 
    Av = (1./(kRv*kRv)) - im/kRv 

    # Vectorized calculation for: "nxn = np.outer(n, n)" 
    nxnv = nv[:,:,:,None]*nv[:,:,None,:] 

    # Vectorized calculation for: "(3*A-1)*nxn + (1-A)*I" 
    P = (3*Av[:,:,None,None]-1)*nxnv + (1-Av[:,:,None,None])*I 

    # Vectorized calculation for: "-alpha*(k2*np.exp(im*kR))/R"  
    multv = -alpha*(k2*np.exp(im*kRv))/Rv 

    # Vectorized calculation for: "nxn *= -alpha*(k2*np.exp(im*kR))/R" 
    outv = P*multv[:,:,None,None] 


    # Simulate ELSE part of the conditional statement"if i != j:" 
    # with masked setting to I on the last two dimensions 
    outv[np.eye((N),dtype=bool)] = I 

    return outv.transpose(0,2,1,3).reshape(N*3,-1) 

kiểm tra Runtime và xác minh đầu ra -

Case # 1:

In [703]: N = 10 
    ...: s = np.random.rand(N,3) + complex(0,1)*np.random.rand(N,3) 
    ...: alpha = 3j 
    ...: kprop = 5.4 
    ...: 

In [704]: out_org = interaction(s,alpha,kprop) 
    ...: out_vect = vectorized_interaction(s,alpha,kprop) 
    ...: print np.allclose(np.real(out_org),np.real(out_vect)) 
    ...: print np.allclose(np.imag(out_org),np.imag(out_vect)) 
    ...: 
True 
True 

In [705]: %timeit interaction(s,alpha,kprop) 
100 loops, best of 3: 7.6 ms per loop 

In [706]: %timeit vectorized_interaction(s,alpha,kprop) 
1000 loops, best of 3: 304 µs per loop 

Case # 2:

In [707]: N = 100 
    ...: s = np.random.rand(N,3) + complex(0,1)*np.random.rand(N,3) 
    ...: alpha = 3j 
    ...: kprop = 5.4 
    ...: 

In [708]: out_org = interaction(s,alpha,kprop) 
    ...: out_vect = vectorized_interaction(s,alpha,kprop) 
    ...: print np.allclose(np.real(out_org),np.real(out_vect)) 
    ...: print np.allclose(np.imag(out_org),np.imag(out_vect)) 
    ...: 
True 
True 

In [709]: %timeit interaction(s,alpha,kprop) 
1 loops, best of 3: 826 ms per loop 

In [710]: %timeit vectorized_interaction(s,alpha,kprop) 
100 loops, best of 3: 14 ms per loop 

Trường hợp # 3:

In [711]: N = 900 
    ...: s = np.random.rand(N,3) + complex(0,1)*np.random.rand(N,3) 
    ...: alpha = 3j 
    ...: kprop = 5.4 
    ...: 

In [712]: out_org = interaction(s,alpha,kprop) 
    ...: out_vect = vectorized_interaction(s,alpha,kprop) 
    ...: print np.allclose(np.real(out_org),np.real(out_vect)) 
    ...: print np.allclose(np.imag(out_org),np.imag(out_vect)) 
    ...: 
True 
True 

In [713]: %timeit interaction(s,alpha,kprop) 
1 loops, best of 3: 1min 7s per loop 

In [714]: %timeit vectorized_interaction(s,alpha,kprop) 
1 loops, best of 3: 1.59 s per loop 
+0

Điều này hoạt động rất tốt và cũng là một nền giáo dục tuyệt vời cho tôi theo cách để vector hóa. Một vấn đề mặc dù: cảnh báo được tạo ra mối quan tâm chia cho số không, có lẽ tương ứng với trường hợp i = j, kể từ R là sau đó 0. Bất kỳ cách nào để thực hiện cùng một điều mà không tạo ra những cảnh báo?Tôi nhận ra rằng các yếu tố bị ảnh hưởng của mảng bị ghi đè trong dòng cuối cùng trước khi trở về, vì vậy có lẽ tôi chỉ phải ép buộc bản thân mình bỏ qua chúng. –

+0

Một nhận xét khác: phiên bản vectơ có vẻ đòi hỏi nhiều bộ nhớ hơn phiên bản gốc, vì vậy khi tôi nâng N lên 9000, quá trình này dường như muốn có ít nhất 50 GB (có 16 GB trong máy của tôi). Có lẽ đây là một sự cân bằng không thể tránh khỏi với vectorization. –

+1

@GrantPetty Có, bạn cần phải bỏ qua những cảnh báo đó vì chúng tôi đang thiết lập các giá trị đó vào cuối. Ngoài ra, bạn nói đúng, đó là giao dịch với vector hóa mà bạn đang sử dụng nhiều bộ nhớ hơn, nhưng đó là lý do tại sao vector hóa là tốt, bởi vì nó lưu trữ tất cả các phần tử đó trong bộ nhớ và làm mọi thứ trong một lần. – Divakar

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