มีnumexpr , numbaและcythonอยู่รอบ ๆ เป้าหมายของคำตอบนี้คือการคำนึงถึงความเป็นไปได้เหล่านี้
แต่ก่อนอื่นเราจะระบุให้ชัดเจน: ไม่ว่าคุณจะจับคู่ฟังก์ชั่นของ Python ลงบน numpy-array ได้อย่างไรมันยังคงเป็นฟังก์ชั่น Python ซึ่งหมายความว่าสำหรับการประเมินทุกครั้ง
- องค์ประกอบ numpy-array ต้องถูกแปลงเป็น Python-object (เช่น a
Float
)
- การคำนวณทั้งหมดเสร็จสิ้นด้วย Python-objects ซึ่งหมายความว่ามีค่าใช้จ่ายของ interpreter, การจัดส่งแบบไดนามิกและวัตถุที่ไม่เปลี่ยนรูป
ดังนั้นเครื่องจักรที่ใช้ในการวนซ้ำจริง ๆ อาร์เรย์ไม่ได้มีบทบาทใหญ่เพราะค่าใช้จ่ายดังกล่าวข้างต้น - มันยังคงช้ากว่าการใช้ฟังก์ชั่นในตัวของ numpy
ลองมาดูตัวอย่างต่อไปนี้:
# 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
ถูกเลือกเป็นตัวแทนของคลาสฟังก์ชันของ pure-python การใช้perfplot
(ดูรหัสในภาคผนวกของคำตอบนี้) เราจะได้เวลาทำงานต่อไปนี้:
เราสามารถเห็นได้ว่าวิธีการของ numpy นั้นเร็วกว่ารุ่น python แท้ถึง 10x-100x การลดลงของประสิทธิภาพสำหรับขนาดอาร์เรย์ที่ใหญ่กว่าอาจเป็นเพราะข้อมูลไม่เหมาะกับแคชอีกต่อไป
มันเป็นสิ่งที่ควรค่าแก่การกล่าวถึงซึ่งvectorize
ใช้หน่วยความจำจำนวนมากดังนั้นบ่อยครั้งที่การใช้หน่วยความจำคือคอขวด (ดูคำถามที่เกี่ยวข้องกับSO ) นอกจากนี้โปรดทราบว่าเอกสารของ numpy np.vectorize
ระบุว่าเป็น "ให้ไว้เพื่อความสะดวกเป็นหลักไม่ใช่เพื่อประสิทธิภาพ"
ควรใช้เครื่องมืออื่นเมื่อต้องการประสิทธิภาพนอกเหนือจากการเขียน C-extension ตั้งแต่เริ่มต้นแล้วมีความเป็นไปได้ดังต่อไปนี้:
หนึ่งมักจะได้ยินว่าประสิทธิภาพของก้อนนั้นดีเท่าที่จะได้รับเพราะมันเป็นซีบริสุทธิ์ภายใต้ประทุน ยังมีห้องพักจำนวนมากสำหรับการปรับปรุง!
vectorized numpy-version ใช้หน่วยความจำเพิ่มเติมและการเข้าถึงหน่วยความจำจำนวนมาก Numexp-library พยายามเรียงไพ่ numpy-arrays และทำให้การใช้งานแคชดีขึ้น:
# 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")
นำไปสู่การเปรียบเทียบต่อไปนี้:
ฉันไม่สามารถอธิบายทุกอย่างในพล็อตด้านบน: เราสามารถเห็นค่าใช้จ่ายที่ใหญ่กว่าสำหรับ numexpr-library ในตอนเริ่มต้น แต่เนื่องจากมันใช้แคชได้ดีกว่าจึงเร็วกว่าประมาณ 10 เท่าสำหรับอาร์เรย์ที่ใหญ่กว่า!
อีกวิธีหนึ่งคือการคอมไพล์ฟังก์ชั่นของ jit และทำให้ได้ UF-pure แท้ นี่คือแนวทางของ 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
มันเร็วกว่า numpy-approach 10 เท่า:
อย่างไรก็ตามงานนั้นขนานได้อย่างน่าอับอายดังนั้นเราสามารถใช้prange
เพื่อคำนวณลูปในแบบขนาน:
@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
ฟังก์ชั่นขนานจะช้ากว่าสำหรับอินพุตที่เล็กกว่า แต่เร็วกว่า (เกือบ 2 เท่า) สำหรับขนาดที่ใหญ่กว่า:
ในขณะที่ numba เชี่ยวชาญในการปรับการทำงานให้เหมาะสมกับ numpy-arrays แต่ Cython เป็นเครื่องมือทั่วไป มันมีความซับซ้อนมากขึ้นในการดึงประสิทธิภาพเช่นเดียวกับ numba - บ่อยครั้งที่มันจะลงไปที่ lvm (numba) กับคอมไพเลอร์ท้องถิ่น (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 ส่งผลให้ฟังก์ชั่นค่อนข้างช้า:
ข้อสรุป
เห็นได้ชัดว่าการทดสอบเพียงหนึ่งฟังก์ชั่นไม่ได้พิสูจน์อะไรเลย สิ่งหนึ่งที่ควรระลึกไว้เช่นสำหรับฟังก์ชั่นที่เลือกไว้แบนด์วิดท์ของหน่วยความจำคือคอขวดสำหรับขนาดที่ใหญ่กว่า 10 ^ 5 องค์ประกอบ - ดังนั้นเราจึงมีประสิทธิภาพเหมือนกันสำหรับ numba, numexpr และ cython ในภูมิภาคนี้
ในที่สุดคำตอบสุดท้ายจะขึ้นอยู่กับประเภทของฟังก์ชั่นฮาร์ดแวร์การกระจายแบบ Python และปัจจัยอื่น ๆ ยกตัวอย่างเช่นงูกระจายใช้ของ Intel VML สำหรับฟังก์ชั่น numpy และจึงมีประสิทธิภาพเหนือกว่า numba (เว้นแต่จะใช้ SVML, เห็นนี้SO-โพสต์ ) ได้อย่างง่ายดายสำหรับฟังก์ชั่นยอดเยี่ยมชอบexp
, sin
, cos
และที่คล้ายกัน - ดูเช่นต่อไปนี้SO-โพสต์
จากการตรวจสอบนี้และจากประสบการณ์ของฉันจนถึงตอนนี้ฉันจะบอกว่า numba ดูเหมือนจะเป็นเครื่องมือที่ง่ายที่สุดที่มีประสิทธิภาพดีที่สุดตราบใดที่ไม่มีฟังก์ชั่นยอดเยี่ยม
พล็อตเวลาทำงานด้วยperfplot - แพ็คเกจ :
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)'
)