Khi mảng 2d (hoặc mảng thứ hai) là C- hoặc F-liền kề, thì nhiệm vụ ánh xạ một hàm lên mảng 2d thực tế giống như nhiệm vụ ánh xạ một hàm lên mảng 1d - chúng ta chỉ phải xem nó theo cách đó, ví dụ thông qua np.ravel(A,'K')
.
Giải pháp có thể cho mảng 1d đã được thảo luận ví dụ ở đây .
Tuy nhiên, khi bộ nhớ của mảng 2d không liền kề nhau, thì tình huống sẽ phức tạp hơn một chút, vì người ta muốn tránh các lỗi bộ nhớ cache có thể xảy ra nếu trục bị xử lý sai.
Numpy đã có sẵn một máy móc để xử lý các trục theo thứ tự tốt nhất có thể. Một khả năng để sử dụng máy móc này là np.vectorize
. Tuy nhiên, 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" - một chức năng python chậm giữ chức năng python chậm với toàn bộ chi phí liên quan! Một vấn đề khác là mức tiêu thụ bộ nhớ khổng lồ của nó - xem ví dụ bài SO này .
Khi một người muốn có hiệu suất của chức năng C nhưng sử dụng máy móc của numpy, một giải pháp tốt là sử dụng numba để tạo ufuncs, ví dụ:
# 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ó dễ dàng đập np.vectorize
nhưng cũng có khi chức năng tương tự sẽ được thực hiện dưới dạng nhân / bổ sung mảng numpy, tức là
# 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"
Xem phụ lục của câu trả lời này để biết mã đo thời gian:
Phiên bản của Numba (màu xanh lá cây) nhanh hơn khoảng 100 lần so với chức năng python (tức là np.vectorize
), điều này không đáng ngạc nhiên. Nhưng nó cũng nhanh hơn khoảng 10 lần so với chức năng numpy, bởi vì phiên bản numbas không cần mảng trung gian và do đó sử dụng bộ đệm hiệu quả hơn.
Mặc dù cách tiếp cận ufunc của numba là một sự đánh đổi tốt giữa khả năng sử dụng và hiệu suất, nhưng nó vẫn không phải là cách tốt nhất chúng ta có thể làm. Tuy nhiên, không có viên đạn bạc hoặc cách tiếp cận tốt nhất cho bất kỳ nhiệm vụ nào - người ta phải hiểu giới hạn là gì và làm thế nào để giảm thiểu chúng.
Ví dụ, đối với chức năng siêu việt (ví dụ exp
, sin
, cos
) numba không cung cấp bất kỳ lợi thế hơn NumPy của np.exp
(không có mảng tạm thời tạo - nguồn chính của tăng tốc). Tuy nhiên, bản cài đặt Anaconda của tôi sử dụng VML của Intel cho các vectơ lớn hơn 8192 - nó chỉ không thể làm điều đó nếu bộ nhớ không liền kề. Vì vậy, tốt hơn là sao chép các phần tử vào bộ nhớ liền kề để có thể sử dụng VML của Intel:
import numba as nb
@nb.vectorize(target="cpu")
def nb_vexp(x):
return np.exp(x)
def np_copy_exp(x):
copy = np.ravel(x, 'K')
return np.exp(copy).reshape(x.shape)
Để công bằng cho việc so sánh, tôi đã tắt tính năng song song hóa của VML (xem mã trong phần phụ lục):
Như mọi người có thể thấy, một khi VML khởi động, chi phí sao chép sẽ được bù nhiều hơn. Tuy nhiên, một khi dữ liệu trở nên quá lớn đối với bộ đệm L3, lợi thế là tối thiểu khi tác vụ trở lại giới hạn băng thông bộ nhớ.
Mặt khác, numba cũng có thể sử dụng SVML của Intel, như được giải thích trong bài viết này :
from llvmlite import binding
# set before import
binding.set_option('SVML', '-vector-library=SVML')
import numba as nb
@nb.vectorize(target="cpu")
def nb_vexp_svml(x):
return np.exp(x)
và sử dụng VML với năng suất song song:
Phiên bản của numba có ít chi phí hoạt động hơn, nhưng đối với một số kích thước, VML đánh bại SVML ngay cả khi có thêm chi phí sao chép - điều này không gây ngạc nhiên chút nào vì ufuncs của numba không song song.
Danh sách:
A. so sánh hàm đa thức:
import perfplot
perfplot.show(
setup=lambda n: np.random.rand(n,n)[::2,::2],
n_range=[2**k for k in range(0,12)],
kernels=[
f,
vf,
nb_vf
],
logx=True,
logy=True,
xlabel='len(x)'
)
B. so sánh về exp
:
import perfplot
import numexpr as ne # using ne is the easiest way to set vml_num_threads
ne.set_vml_num_threads(1)
perfplot.show(
setup=lambda n: np.random.rand(n,n)[::2,::2],
n_range=[2**k for k in range(0,12)],
kernels=[
nb_vexp,
np.exp,
np_copy_exp,
],
logx=True,
logy=True,
xlabel='len(x)',
)