Có numexpr , numba và cython , mục tiêu của câu trả lời này là xem xét các khả năng này.
Nhưng trước tiên, hãy nêu rõ ràng: cho dù bạn ánh xạ hàm Python lên mảng numpy như thế nào, thì nó vẫn là hàm Python, có nghĩa là cho mọi đánh giá:
- phần tử numpy-Array phải được chuyển đổi thành đối tượng Python (ví dụ a
Float
).
- tất cả các tính toán được thực hiện với các đối tượng Python, có nghĩa là có chi phí hoạt động của trình thông dịch, công văn động và các đối tượng không thay đổi.
Vì vậy, máy móc nào được sử dụng để thực sự lặp qua mảng không đóng vai trò lớn vì chi phí được đề cập ở trên - nó chậm hơn nhiều so với sử dụng chức năng tích hợp của numpy.
Hãy xem ví dụ sau:
# numpy-functionality
def f(x):
return x+2*x*x+4*x*x*x
# python-function as ufunc
import numpy as np
vf=np.vectorize(f)
vf.__name__="vf"
np.vectorize
được chọn làm đại diện cho lớp tiếp cận hàm python thuần. Sử dụng perfplot
(xem mã trong phần phụ lục của câu trả lời này) chúng ta sẽ có được thời gian chạy sau:
Chúng ta có thể thấy, cách tiếp cận numpy nhanh hơn gấp 10 lần so với phiên bản python. Việc giảm hiệu năng cho kích thước mảng lớn hơn có lẽ là do dữ liệu không còn vừa với bộ đệm.
Điều đáng nói là, nó vectorize
cũng sử dụng rất nhiều bộ nhớ, vì vậy thường sử dụng bộ nhớ là cổ chai (xem câu hỏi SO liên quan ). Cũng lưu ý rằng tài liệu của numpy np.vectorize
nói rằng nó "được cung cấp chủ yếu để thuận tiện, không phải cho hiệu suất".
Các công cụ khác nên được sử dụng, khi hiệu suất được mong muốn, bên cạnh việc viết phần mở rộng C từ đầu, có các khả năng sau:
Mọi người thường nghe rằng hiệu suất numpy tốt như nó có được, bởi vì nó là C thuần túy dưới mui xe. Tuy nhiên, có rất nhiều phòng để cải thiện!
Phiên bản numpy được vector hóa sử dụng rất nhiều bộ nhớ và truy cập bộ nhớ bổ sung. Thư viện số mở rộng cố gắng xếp các mảng numpy và do đó sử dụng bộ đệm tốt hơn:
# less cache misses than numpy-functionality
import numexpr as ne
def ne_f(x):
return ne.evaluate("x+2*x*x+4*x*x*x")
Dẫn đến sự so sánh sau:
Tôi không thể giải thích mọi thứ trong cốt truyện ở trên: chúng ta có thể thấy chi phí lớn hơn cho thư viện numexpr lúc đầu, nhưng vì nó sử dụng bộ đệm tốt hơn nên nhanh hơn khoảng 10 lần cho các mảng lớn hơn!
Một cách tiếp cận khác là biên dịch hàm và do đó nhận được một UFunc thuần C thực sự. Đây là cách tiếp cận của Numba:
# runtime generated C-function as ufunc
import numba as nb
@nb.vectorize(target="cpu")
def nb_vf(x):
return x+2*x*x+4*x*x*x
Nó nhanh hơn 10 lần so với phương pháp tiếp cận ban đầu:
Tuy nhiên, nhiệm vụ là song song lúng túng, do đó chúng ta cũng có thể sử dụng prange
để tính toán vòng lặp song song:
@nb.njit(parallel=True)
def nb_par_jitf(x):
y=np.empty(x.shape)
for i in nb.prange(len(x)):
y[i]=x[i]+2*x[i]*x[i]+4*x[i]*x[i]*x[i]
return y
Như mong đợi, chức năng song song chậm hơn đối với đầu vào nhỏ hơn, nhưng nhanh hơn (gần như yếu tố 2) đối với kích thước lớn hơn:
Trong khi numba chuyên tối ưu hóa các hoạt động với mảng numpy, Cython là một công cụ tổng quát hơn. Nó phức tạp hơn để trích xuất hiệu suất tương tự như với numba - thường thì nó sẽ giảm xuống llvm (numba) so với trình biên dịch cục bộ (gcc / MSVC):
%%cython -c=/openmp -a
import numpy as np
import cython
#single core:
@cython.boundscheck(False)
@cython.wraparound(False)
def cy_f(double[::1] x):
y_out=np.empty(len(x))
cdef Py_ssize_t i
cdef double[::1] y=y_out
for i in range(len(x)):
y[i] = x[i]+2*x[i]*x[i]+4*x[i]*x[i]*x[i]
return y_out
#parallel:
from cython.parallel import prange
@cython.boundscheck(False)
@cython.wraparound(False)
def cy_par_f(double[::1] x):
y_out=np.empty(len(x))
cdef double[::1] y=y_out
cdef Py_ssize_t i
cdef Py_ssize_t n = len(x)
for i in prange(n, nogil=True):
y[i] = x[i]+2*x[i]*x[i]+4*x[i]*x[i]*x[i]
return y_out
Cython kết quả trong các chức năng hơi chậm:
Phần kết luận
Rõ ràng, thử nghiệm chỉ cho một chức năng không chứng minh bất cứ điều gì. Ngoài ra, bạn nên nhớ rằng, đối với ví dụ về chức năng được chọn, băng thông của bộ nhớ là cổ chai cho các kích thước lớn hơn 10 ^ 5 phần tử - do đó chúng tôi có cùng hiệu suất cho numba, numexpr và cython trong khu vực này.
Cuối cùng, câu trả lời tối ưu phụ thuộc vào loại chức năng, phần cứng, phân phối Python và các yếu tố khác. Ví dụ Anaconda-phân phối sử dụng VML của Intel cho các chức năng NumPy và do đó nhanh hơn so với numba (trừ khi nó sử dụng SVML, thấy điều này SO-bài ) dễ dàng cho các chức năng siêu việt như thế exp
, sin
, cos
và tương tự - xem ví dụ sau -SO bài .
Tuy nhiên, từ cuộc điều tra này và từ kinh nghiệm của tôi cho đến nay, tôi sẽ nói rằng, numba dường như là công cụ dễ nhất với hiệu suất tốt nhất miễn là không có chức năng siêu việt nào.
Vẽ sơ đồ thời gian chạy với perfplot -package :
import perfplot
perfplot.show(
setup=lambda n: np.random.rand(n),
n_range=[2**k for k in range(0,24)],
kernels=[
f,
vf,
ne_f,
nb_vf, nb_par_jitf,
cy_f, cy_par_f,
],
logx=True,
logy=True,
xlabel='len(x)'
)