เมื่อ 2d-array (หรือ nd-array) เป็น C- หรือ F-contiguous งานนี้ในการแมปฟังก์ชันลงบนอาร์เรย์ 2d จะเหมือนกับงานการแมปฟังก์ชันลงในอาร์เรย์ 1d - เราแค่ np.ravel(A,'K')
ต้องดูมันเป็นอย่างนั้นเช่นผ่านทาง
วิธีการแก้ปัญหาที่เป็นไปได้สำหรับ 1D อาร์เรย์ได้รับการกล่าวเช่นที่นี่
อย่างไรก็ตามเมื่อหน่วยความจำของ 2d-array ไม่ติดกันสถานการณ์จะซับซ้อนขึ้นเล็กน้อยเพราะเราต้องการหลีกเลี่ยงการพลาดแคชที่เป็นไปได้หากแกนถูกจัดการในลำดับที่ไม่ถูกต้อง
Numpy มีเครื่องจักรอยู่แล้วในการประมวลผลแกนตามลำดับที่ดีที่สุด np.vectorize
หนึ่งเป็นไปได้ที่จะใช้เครื่องจักรนี้อยู่ อย่างไรก็ตามเอกสารของ numpy np.vectorize
ระบุว่า "จัดทำขึ้นเพื่อความสะดวกเป็นหลักไม่ใช่เพื่อประสิทธิภาพ" - ฟังก์ชัน python ที่ช้าจะยังคงเป็นฟังก์ชัน python ที่ช้าพร้อมกับค่าโสหุ้ยที่เกี่ยวข้องทั้งหมด! อีกปัญหาหนึ่งคือการใช้หน่วยความจำอย่างมาก - ดูตัวอย่างSO-postนี้
เมื่อต้องการมีประสิทธิภาพของ C-function แต่ต้องการใช้เครื่องจักรของ numpy วิธีแก้ปัญหาที่ดีคือการใช้ numba ในการสร้าง ufuncs เช่น:
# 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
มันเต้นได้อย่างง่ายดายnp.vectorize
แต่ยังเมื่อฟังก์ชันเดียวกันจะถูกดำเนินการเป็นการคูณ / การเพิ่มจำนวนอาร์เรย์เช่น
# 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"
ดูภาคผนวกของคำตอบนี้สำหรับรหัสการวัดเวลา:
เวอร์ชันของ Numba (สีเขียว) เร็วกว่า python-function (ie np.vectorize
) ประมาณ 100 เท่าซึ่งไม่น่าแปลกใจ แต่มันยังเร็วกว่าฟังก์ชัน numpy ประมาณ 10 เท่าเนื่องจากเวอร์ชัน numas ไม่ต้องการอาร์เรย์กลางจึงใช้แคชได้อย่างมีประสิทธิภาพมากขึ้น
แม้ว่าแนวทาง ufunc ของ numba จะเป็นการแลกเปลี่ยนที่ดีระหว่างความสามารถในการใช้งานและประสิทธิภาพ แต่ก็ยังไม่ดีที่สุดที่เราสามารถทำได้ ยังไม่มีกระสุนเงินหรือแนวทางที่ดีที่สุดสำหรับงานใด ๆ - เราต้องเข้าใจว่าอะไรคือข้อ จำกัด และวิธีที่จะบรรเทาได้
ตัวอย่างเช่นสำหรับฟังก์ชั่นยอดเยี่ยม (เช่นexp
, sin
, cos
) numba ไม่ได้ให้ประโยชน์ใด ๆ มากกว่า numpy ของnp.exp
(ไม่มีอาร์เรย์ชั่วคราวที่สร้างขึ้น - แหล่งที่มาหลักของความเร็วขึ้นไป) อย่างไรก็ตามการติดตั้ง Anaconda ของฉันใช้ VML ของ Intel สำหรับเวกเตอร์ที่มีขนาดใหญ่กว่า 8192ซึ่งไม่สามารถทำได้หากหน่วยความจำไม่ติดกัน ดังนั้นจึงอาจเป็นการดีกว่าที่จะคัดลอกองค์ประกอบไปยังหน่วยความจำที่อยู่ติดกันเพื่อให้สามารถใช้ VML ของ 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)
เพื่อความเป็นธรรมของการเปรียบเทียบฉันได้ปิดการขนานของ VML แล้ว (ดูรหัสในภาคผนวก):
อย่างที่เห็นเมื่อ VML เริ่มต้นค่าใช้จ่ายในการคัดลอกจะได้รับการชดเชยมากกว่า เมื่อข้อมูลมีขนาดใหญ่เกินไปสำหรับแคช L3 ข้อได้เปรียบก็จะน้อยที่สุดเนื่องจากงานกลายเป็นอีกครั้งที่มีการ จำกัด แบนด์วิธหน่วยความจำ
ในทางกลับกัน numba สามารถใช้ SVML ของ Intel ได้เช่นกันตามที่อธิบายไว้ในโพสต์นี้ :
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)
และการใช้ VML กับผลตอบแทนแบบขนาน:
เวอร์ชันของ numba มีค่าใช้จ่ายน้อยกว่า แต่สำหรับ VML บางขนาดจะชนะ SVML แม้จะมีค่าใช้จ่ายในการคัดลอกเพิ่มเติมซึ่งไม่น่าแปลกใจเลยเพราะ ufuncs ของ numba ไม่ได้ขนานกัน
รายชื่อ:
ก. การเปรียบเทียบฟังก์ชันพหุนาม:
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)'
)
ข. การเปรียบเทียบ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)',
)