เร่งสปีด Python ด้วย JIT Numba ช่วยได้เยอะนะครับ
สวัสดีครับ
วันนี้ผมมีเรื่องดีๆ มาฝากเพื่อนๆ โปรแกรมเมอร์ ที่อยากให้โค้ด Python ของเราทำงานเร็วขึ้นแบบก้าวกระโดดนะครับ นั่นก็คือเรื่องของ Just-In-Time (JIT) Compiler โดยเฉพาะตัว Numba ที่ใช้กันบ่อยๆ เลย
หลายคนอาจจะเคยเจอปัญหาโค้ด Python ทำงานช้า โดยเฉพาะพวกที่ต้องคำนวณเยอะๆ หรือวนลูปซับซ้อนใช่ไหมครับ Numba นี่แหละครับ ตัวช่วยสำคัญ
Numba คืออะไร?
อธิบายง่ายๆ นะครับ Numba เนี่ย มันคือ JIT Compiler ที่จะแปลงโค้ด Python ของเรา ให้กลายเป็นโค้ดเครื่อง (machine code) ตอนที่โค้ดรันครั้งแรก
จากนั้นเวลาเราเรียกใช้ฟังก์ชันเดิมอีก โค้ดมันก็จะทำงานเร็วขึ้นมากๆ เลย เพราะไม่ต้องแปลใหม่แล้วไงครับ เจ๋งไหม
มาดูตัวอย่างโค้ดกันเลยดีกว่า
ผมมีตัวอย่างง่ายๆ มาให้ดูนะครับ เราจะลองเขียนฟังก์ชันคำนวณ Fibonacci แบบปกติ กับแบบใช้ Numba
import time
from numba import jit
# ฟังก์ชัน Fibonacci แบบธรรมดา
def fib_normal(n):
a, b = 0, 1
for _ in range(n):
a, b = b, a + b
return a
# ฟังก์ชัน Fibonacci แบบใช้ Numba JIT
@jit(nopython=True)
def fib_numba(n):
a, b = 0, 1
for _ in range(n):
a, b = b, a + b
return a
# ลองวัดเวลาดูครับ
print("ทดสอบ Fibonacci แบบธรรมดา")
start_time = time.time()
result_normal = fib_normal(100000)
end_time = time.time()
print(f"ผลลัพธ์: {result_normal}")
print(f"ใช้เวลา: {end_time - start_time:.4f} วินาที\
")
print("ทดสอบ Fibonacci แบบ Numba JIT")
start_time = time.time()
# รันครั้งแรก Numba จะ compile
result_numba = fib_numba(100000)
end_time = time.time()
print(f"ผลลัพธ์: {result_numba}")
print(f"ใช้เวลา: {end_time - start_time:.4f} วินาที (ครั้งแรก)\
")
print("ทดสอบ Fibonacci แบบ Numba JIT อีกครั้ง (หลังจาก compile แล้ว)")
start_time = time.time()
result_numba_again = fib_numba(100000)
end_time = time.time()
print(f"ผลลัพธ์: {result_numba_again}")
print(f"ใช้เวลา: {end_time - start_time:.4f} วินาที (ครั้งที่สอง)\
")
ผลลัพธ์ที่ได้อาจแตกต่างกันไปตามเครื่องนะครับ แต่ปกติแล้ว Numba จะเร็วกว่าเยอะเลย
จะเห็นว่าแค่เพิ่ม @jit(nopython=True) เข้าไปตรงหัวฟังก์ชัน โค้ดเราก็เร็วขึ้นแบบเห็นได้ชัดเลยใช่ไหมครับ ตรง nopython=True เนี่ย มันจะบังคับให้ Numba แปลงโค้ดเป็น machine code ทั้งหมดนะครับ ถ้ามีอะไรแปลไม่ได้มันจะฟ้อง error เลย เพื่อให้มั่นใจว่าโค้ดเราจะรันได้เร็วที่สุด
เรื่องของ Resilience นิดหน่อย
การใช้ JIT Compiler อย่าง Numba เนี่ยครับ มันช่วยให้โค้ดเรา optimization ได้ดีก็จริง แต่บางทีก็ต้องระวังนิดนึงนะครับ ถ้าโค้ดเรามีความซับซ้อนมากๆ เช่น ใช้โครงสร้างข้อมูลที่ไม่รองรับ หรือไปเรียกใช้ฟังก์ชัน Python ปกติเยอะเกินไป Numba อาจจะ compile ไม่ได้ หรือทำงานได้ไม่เต็มประสิทธิภาพนะครับ
เราก็ต้องเขียนโค้ดของเราให้ resilience คือพร้อมรับมือกับพวกสถานการณ์แบบนี้ด้วย
เช่น ถ้าเราไม่แน่ใจว่า Numba จะทำงานกับโค้ดเราได้ดีแค่ไหน เราอาจจะทำเป็น Fallback Mechanism ไว้ก็ได้ครับ คือถ้า Numba มันมีปัญหา หรือทำงานช้ากว่าที่คาด เราก็ยังสามารถสลับไปใช้เวอร์ชันที่ไม่ใช้ Numba ได้ เพื่อให้ระบบของเรายังทำงานต่อได้ ไม่ล่มง่ายๆ นะครับ
อีกเรื่องคือการ error handling นะครับ เวลาใช้ Numba ก็ควรจะมี try-except บล็อกดีๆ เผื่อกรณีที่ Numba มัน compile ไม่ผ่าน หรือเกิด runtime error ที่ไม่คาดคิดนะครับ จะได้ไม่ทำให้โปรแกรมเรา crash ไปเลย
from numba import jit
from numba.core import errors
@jit(nopython=True)
def calculate_complex_data(data_list):
# สมมติว่ามีบางอย่างที่ Numba อาจจะไม่ชอบ หรือข้อมูลมีปัญหา
total = 0
for item in data_list:
total += item * 2 # อาจจะมีปัญหาถ้า item ไม่ใช่ตัวเลข
return total
def safe_calculate(data_list):
try:
# ลองใช้ Numba ก่อน
print("ลองใช้ Numba JIT...")
return calculate_complex_data(data_list)
except errors.TypingError as e:
# ถ้า Numba มีปัญหาในการแปลงโค้ด
print(f"Numba Compile Error: {e} - ใช้ฟังก์ชัน Python ปกติแทน")
# Fallback ไปใช้เวอร์ชันที่ไม่ใช้ Numba
total = 0
for item in data_list:
total += item * 2
return total
except Exception as e:
# ข้อผิดพลาดอื่นๆ
print(f"เกิดข้อผิดพลาดอื่น: {e} - ใช้ฟังก์ชัน Python ปกติแทน")
total = 0
for item in data_list:
total += item * 2
return total
# ทดสอบแบบที่ข้อมูลปกติ
print("--- ทดสอบข้อมูลปกติ ---")
result1 = safe_calculate([1, 2, 3, 4, 5])
print(f"ผลลัพธ์: {result1}\
")
# ทดสอบแบบที่ Numba อาจจะมีปัญหา (เช่น ข้อมูลปนกัน)
print("--- ทดสอบข้อมูลที่มีปัญหา (Numba อาจจะฟ้อง error) ---")
# Numba nopython=True จะรับไม่ได้ถ้ามี string ปน
result2 = safe_calculate([1, 2, 'a', 4, 5])
print(f"ผลลัพธ์: {result2}\
")
เห็นไหมครับ การมี resilience ที่ดี ก็ช่วยให้โปรแกรมเราแข็งแรงขึ้นเยอะเลยนะครับ แม้จะใช้เทคนิค optimization ที่ซับซ้อนอย่าง jit compiler ก็ตาม
ผมหวังว่าบทความนี้จะเป็นประโยชน์กับเพื่อนๆ นะครับ ลองเอา Numba ไปเล่นดูได้เลย แล้วจะเห็นความต่างเลยครับ
สวัสดีครับ cii3.net
Sources/References: - Numba Documentation: https://numba.pydata.org/