پایتون سالهاست که زبان پیشفرض برای علم داده، یادگیری ماشین و محاسبات علمی است. با این حال، ماهیت تفسیرشده آن و قفل تفسیرگر سراسری (GIL) میتواند هنگام کار با حلقههای عددی سنگین یا مجموعهدادههای بزرگ، به گلوگاههای عملکردی قابل توجهی منجر شود. در حالی که کتابخانههایی مانند NumPy و Pandas در زیرلایه با زبان C نوشته شدهاند، منطق سفارشی پایتون اغلب با سرعتی کسری از آنچه برای پردازش بلادرنگ یا شبیهسازیهای عظیم نیاز است، اجرا میشود.
برای توسعهدهندگان متوسط تا پیشرفته، راهحل در پر کردن شکاف بین سادگی استفاده از پایتون و سرعت خام C نهفته است. دو ابزار قدرتمند در این مجموعه، Numba و Cython هستند. هر دو به شما اجازه میدهند کد پایتون را بدون رها کردن این زبان شتاب دهید، اما رویکرد متفاوتی برای حل مسئله اتخاذ میکنند. این پست بررسی میکند که چگونه میتوان از این فناوریها برای دستیابی به شتابهای عددی قابل توجه استفاده کرد.
درک شکاف عملکردی
قبل از ورود به ابزارها، درک این نکته ضروری است که چرا پایتون کند است. CPython کد را خط به خط اجرا میکند و برای هر عملیات، بررسیهای نوع پویا و تخصیص حافظه را انجام میدهد. در یک حلقه ساده که یک دنباله ریاضی را محاسبه میکند، سربار تفسیر بایتکد میتواند زمان محاسبه واقعی را تحتالشعاع قرار دهد. به عنوان مثال، یک حلقه که یک میلیون بار برای جمعکردن اعداد مربعی تکرار میشود، ممکن است در پایتون خالص چندین ثانیه طول بکشد، در حالی که معادل کامپایلشده آن در C در چند میلیثانیه اجرا میشود.
برای بهینهسازی، به طور کلی سه مسیر وجود دارد: بازنویسی در C/C++، استفاده از عملیات برداریشده NumPy، یا استفاده از کتابخانههای شتابدهنده. وقتی برداریسازی به دلیل منطق پیچیده امکانپذیر نیست، Numba و Cython به گزینههای اصلی تبدیل میشوند.
Numba: کامپایل لحظهای (JIT) به سادگی
Numba یک کامپایلر باز منبع لحظهای (JIT) است که زیرمجموعهای از کد پایتون و NumPy را به کد ماشین سریع ترجمه میکند. ویژگی برجسته آن نیاز به تغییر حداقل کد است: معمولاً فقط کافی است یک دکوراتور اضافه کنید.
Numba در الگوریتمهای عددی که منطق آنها ساده اما تکراری است، عالی عمل میکند. این ابزار توابع را در لحظه کامپایل میکند و نسخه کامپایلشده را برای فراخوانیهای بعدی در حافظه پنهان (Cache) نگه میدارد. این ویژگی آن را برای شبیهسازیهای علمی، پردازش سیگنال و عملیات ریاضی سفارشی که با NumPy به تنهایی به راحتی برداریسازی نمیشوند، ایدهآل میکند.
تابع خالص پایتون زیر را در نظر بگیرید:
import numpy as np
import time
def pure_python_sum(x):
total = 0.0
for i in range(len(x)):
total += x[i] * x[i]
return total
برای شتابدهی این تابع با Numba، کافی است دکوراتور @jit را اعمال کنیم:
from numba import jit
@jit(nopython=True)
def numba_sum(x):
total = 0.0
for i in range(len(x)):
total += x[i] * x[i]
return total
با پرچم nopython=True، Numba تابع را به کد ماشینی کامپایل میکند که به تفسیرگر پایتون وابسته نیست و به طور موثر سربار پویا را حذف میکند. در بنچمارکها، این تبدیل ساده میتواند بسته به بار کاری، شتابهایی بین ۱۰ تا ۱۰۰ برابر ایجاد کند.
Cython: کامپایل استاتیک با سینتکس پایتون
اگر Numba درباره بهینهسازی پویا است، Cython درباره کامپایل استاتیک است. Cython یک زیرمجموعه (Superset) از پایتون است که به شما اجازه میدهد کد سازگار با C را با حفظ سینتکس شبیه پایتون بنویسید. شما فایلهای .pyx خود را قبل از اجرا به افزونههای C (.so یا .pyd) کامپایل میکنید.
Cython کنترل دقیقتری نسبت به Numba ارائه میدهد. شما میتوانید برای متغیرها نوعهای استاتیک تعریف کنید، با کتابخانههای C موجود ادغام شوید و استفاده از حافظه را به صورت تهاجمتری بهینه کنید. این ویژگی Cython را به انتخابی ترجیحی برای ساخت ماژولهای افزونه، تعبیه کتابخانههای C یا زمانی که نیاز به توزیع یک باینری بهینه و خودکفا دارید، تبدیل میکند.
در اینجا نحوه نمایش مثال قبلی در Cython با تایپهای استاتیک آمده است:
def cython_sum(double[:] x):
cdef int i
cdef double total = 0.0
for i in range(x.shape[0]):
total += x[i] * x[i]
return total
نماد double[:] به Cython میگوید که ورودی را به عنوان یک نمای حافظه از اعداد دوقطبی (double) در نظر بگیرد و cdef متغیرهای محلی را به عنوان نوعهای C اعلام میکند. هنگامی که با استفاده از یک اسکریپت setup.py یا cythonize کامپایل میشود، این کد C بسیار کارآمدی تولید میکند که با سرعتی نزدیک به بومی اجرا میشود.
چه زمانی از کدام استفاده کنیم؟
انتخاب ابزار مناسب به محدودیتهای خاص پروژه شما بستگی دارد. اگر نیاز به تکرار سریع دارید، میخواهید توابع خاصی را بدون مرحله ساخت شتاب دهید یا در محیط Jupyter Notebook کار میکنید، Numba را انتخاب کنید. این سریعترین راه برای دستیابی به شتاب با کمترین اصطکاک است.
اگر در حال ساخت توزیع کتابخانهای هستید که باید مستقل باشد، به ادغام عمیق با APIهای C/C++ نیاز دارید یا نیاز به مدیریت صریح حافظه دارید، Cython را انتخاب کنید. Cython برای استقرارهای تولیدی که میخواهید یک اثر باینری ثابت به جای کد کامپایلشده در لحظه داشته باشید، مستحکمتر است.
نتیجهگیری
بهینهسازی کد پایتون برای عملکرد عددی نیاز به ترک زبان ندارد. با اعمال استراتژیک Numba برای شتابدهی JIT یا Cython برای کامپایل استاتیک، توسعهدهندگان میتوانند سطوح عملکردی قابل مقایسه با C و Fortran را آزاد کنند. چه در حال پردازش مجموعهدادههای بزرگ، اجرای شبیهسازیهای پیچیده یا ساخت APIهای با عملکرد بالا باشید، درک اینکه چه زمانی و چگونه از این ابزارها استفاده شود، یک مهارت حیاتی برای توسعهدهنده پایتون مدرن است.
با پروفایل کردن کد خود برای شناسایی گلوگاهها شروع کنید و سپس با دکوراتورهای Numba یا توضیحات نوع Cython آزمایش کنید تا حلقههای کند خود را به موتورهای پرسرعت تبدیل کنید.