۳۰ شهریور ۱۴۰۳

Techboy

اخبار و اطلاعات روز تکنولوژی

Cython چیست؟ پایتون با سرعت C

ابر مجموعه ای از پایتون که به C کامپایل می شود، Cython سهولت پایتون را با سرعت کد بومی ترکیب می کند. در اینجا یک راهنمای سریع برای استفاده حداکثری از Cython در برنامه های Python آورده شده است.

ابر مجموعه ای از پایتون که به C کامپایل می شود، Cython سهولت پایتون را با سرعت کد بومی ترکیب می کند. در اینجا یک راهنمای سریع برای استفاده حداکثری از Cython در برنامه های Python آورده شده است.

Python به یکی از راحت‌ترین، مجهزترین و کاملاً مفیدترین زبان‌های برنامه‌نویسی شهرت دارد. سرعت اجرا؟ نه چندان.

وارد Cython شوید. زبان Cython ابر مجموعه ای از پایتون است که کامپایل می شود به C. این باعث افزایش عملکرد می شود که بسته به کار در دست انجام می تواند از چند درصد تا چندین مرتبه بزرگی متغیر باشد. برای کارهای محدود شده توسط انواع شی بومی پایتون، افزایش سرعت زیاد نخواهد بود. اما برای عملیات‌های عددی یا هر عملیاتی که شامل داخلی پایتون نمی‌شود، سود می‌تواند بسیار زیاد باشد.

با Cython، می‌توانید بسیاری از محدودیت‌های بومی پایتون را نادیده بگیرید یا به طور کامل از آنها فراتر بروید—بدون اینکه نیازی به دست کشیدن از سهولت و راحتی پایتون را داشته باشید. در این مقاله، مفاهیم اولیه Cython را مرور می‌کنیم و یک برنامه ساده پایتون ایجاد می‌کنیم که از Cython برای تسریع یکی از عملکردهای آن استفاده می‌کند.

کامپایل پایتون در C

کد پایتون می‌تواند مستقیماً با ماژول‌های C تماس برقرار کند. آن ماژول‌های C می‌توانند کتابخانه‌های C عمومی یا کتابخانه‌هایی باشند که به‌طور خاص برای کار با پایتون ساخته شده‌اند. Cython نوع دوم ماژول را تولید می‌کند: کتابخانه‌های C که با قسمت‌های داخلی پایتون صحبت می‌کنند و می‌توانند با کد پایتون موجود همراه شوند.

کد Cython از نظر طراحی بسیار شبیه کد پایتون است. اگر کامپایلر Cython را با برنامه پایتون تغذیه کنید (Python 2.x و Python 3.x هر دو پشتیبانی می شوند)، Cython آن را همانطور که هست می پذیرد، اما هیچ یک از شتاب های بومی Cython وارد عمل نمی شوند. اما اگر کد پایتون را با یادداشت‌های نوع در نحو خاص Cython تزئین کنید، Cython می‌تواند معادل‌های سریع C را جایگزین اشیاء کند پایتون کند.

توجه داشته باشید که رویکرد Cython افزایشی است. این بدان معناست که یک توسعه‌دهنده می‌تواند با یک برنامه  موجود پایتون شروع کند و با ایجاد تغییرات نقطه‌ای در کد، به جای بازنویسی کل برنامه، سرعت آن را افزایش دهد.

این رویکرد به طور کلی با ماهیت مسائل مربوط به عملکرد نرم افزار مطابقت دارد. در اکثر برنامه‌ها، اکثریت قریب به اتفاق کدهای فشرده CPU در چند نقطه داغ متمرکز می‌شوند—نسخه‌ای از اصل پارتو، همچنین به عنوان قانون “۸۰/۲۰” شناخته می شود. بنابراین، بیشتر کدهای یک برنامه پایتون نیازی به بهینه سازی عملکرد ندارند، فقط چند قطعه حیاتی هستند. شما می توانید به صورت تدریجی آن نقاط داغ را به Cython ترجمه کنید تا در جایی که بیشترین اهمیت را دارد، عملکرد مورد نیاز خود را به دست آورید. بقیه برنامه می تواند بدون نیاز به کار اضافی در پایتون باقی بماند.

نحوه استفاده از Cython

کد زیر را که از مستندات Cython گرفته شده است در نظر بگیرید:


def f(x):
    return x**2-x

def integrate_f(a, b, N):
    s = 0
    dx = (b-a)/N
    for i in range(N):
        s += f(a+i*dx)
    return s * dx

این یک نمونه اسباب بازی است، اجرای نه چندان کارآمد یک عملکرد یکپارچه. به‌عنوان کد پایتون خالص، کند است، زیرا پایتون باید بین انواع عددی بومی ماشین و انواع شی‌های داخلی خودش به عقب و جلو تبدیل شود.

اکنون نسخه Cython همان کد را در نظر بگیرید، با اضافات Cython که زیر آنها مشخص شده است:


cdef double f(double x):
    return x**2-x

def integrate_f(double a, double b, int N):
    cdef int i
    cdef double s, dx
    s = 0
    dx = (b-a)/N
    for i in range(N):
        s += f(a+i*dx)
    return s * dx

اگر انواع متغیر را به صراحت بیان کنیم، هر دو برای پارامترهای تابع و متغیرهای مورد استفاده در بدنه تابع (double، int و غیره)، Cython همه اینها را به C ترجمه می کند. ما همچنین می توانیم استفاده کنیم کلمه کلیدی cdef برای تعریف توابعی که عمدتاً در C برای سرعت بیشتر پیاده‌سازی می‌شوند، اگرچه آن توابع را فقط می‌توان توسط سایر توابع Cython فراخوانی کرد و نه توسط اسکریپت‌های Python. در مثال بالا، فقط integrate_f را می توان با اسکریپت پایتون دیگری فراخوانی کرد، زیرا از def استفاده می کند. توابع cdef از پایتون قابل دسترسی نیستند زیرا C خالص هستند و رابط پایتون ندارند.

Qdrant پایگاه داده برداری مدیریت شده را برای ابرهای ترکیبی ارائه می دهد

توجه داشته باشید که کد واقعی ما چقدر تغییر کرده است. تمام کاری که انجام داده‌ایم این است که اعلان‌های نوع را به کد موجود اضافه کرده‌ایم تا عملکرد قابل توجهی را افزایش دهیم.

معرفی سینتکس “Pure Python” Cython

Cython دو راه برای نوشتن کد خود ارائه می دهد. مثال بالا از سینتکس اصلی Cython استفاده می‌کند که قبل از ظهور سینتکس مدرن Python-hinting ایجاد شده بود. اما یک سینتکس جدیدتر Cython به نام حالت پایتون خالص به شما امکان می‌دهد کدی بنویسید که به نحو خود پایتون نزدیک‌تر است، از جمله اعلان‌های نوع.

کد بالا، با استفاده از حالت پایتون خالص، چیزی شبیه به این خواهد بود:


import cython

@cython.cfunc
def f(x: cython.double) -> cython.double:
    return x**2 - x

def integrate_f(a: cython.double, b: cython.double, N: cython.int):
    s: cython.double = 0
    dx: cython.double = (b - a) / N
    i: cython.int
    for i in range(N):
        s += f(a + i * dx)
    return s * dx

حالت Python خالص Cython کمی ساده‌تر است و همچنین می‌توان آن را توسط ابزارهای پرزدار پایتون پردازش کرد. همچنین به شما امکان می دهد تا کد را همانطور که هست، بدون کامپایل اجرا کنید (اگرچه بدون مزایای سرعت). حتی ممکن است بسته به کامپایل بودن یا نبودن کد به صورت مشروط اجرا شود. متأسفانه، برخی از ویژگی‌های Cython، مانند کار با کتابخانه‌های C خارجی، در حالت Python خالص در دسترس نیستند.

اطلاعات بیشتر در مورد حالت Python خالص Cython

می‌خواهید با Cython بیشتر پیش بروید؟ به معرفی من برای کد نویسی با Cython در حالت Python خالص مراجعه کنید.

مزایای Cython

علاوه بر سرعت بخشیدن به کدهایی که قبلاً نوشته‌اید، Cython چندین مزیت دیگر را نیز به همراه دارد.

عملکرد سریع‌تر کار با کتابخانه‌های C خارجی

بسته‌های پایتون مانند NumPy کتابخانه‌های C را در رابط‌های پایتون می‌پیچانند تا کار با آن‌ها آسان شود. با این حال، رفت و برگشت بین Python و C از طریق آن wrapper ها می تواند سرعت کار را کاهش دهد. Cython به شما امکان می‌دهد مستقیماً با کتابخانه‌های زیرین صحبت کنید بدون پایتون. (کتابخانه های C++ نیز پشتیبانی می شوند.)

می توانید از مدیریت حافظه C و Python استفاده کنید

اگر از اشیاء پایتون استفاده می‌کنید، آنها مانند پایتون معمولی با حافظه مدیریت و جمع‌آوری می‌شوند. در صورت تمایل، می‌توانید ساختارهای سطح C خود را ایجاد و مدیریت کنید و از malloc/free برای کار با آنها استفاده کنید. فقط به یاد داشته باشید که خودتان را تمیز کنید.

در صورت نیاز می‌توانید ایمنی یا سرعت را انتخاب کنید

Cython به طور خودکار بررسی‌های زمان اجرا را برای مشکلات رایجی که در C ظاهر می‌شوند، مانند دسترسی خارج از محدوده در یک آرایه، از طریق دکوراتورها و دستورالعمل‌های کامپایلر انجام می‌دهد (به عنوان مثال، @boundscheck(False)). در نتیجه، کد C تولید شده توسط Cython به‌طور پیش‌فرض بسیار ایمن‌تر از کد C دستی است، اگرچه به‌طور بالقوه به قیمت عملکرد خام.

اگر مطمئن هستید که در زمان اجرا به آن بررسی‌ها نیاز ندارید، می‌توانید آن‌ها را برای افزایش سرعت بیشتر، چه در کل ماژول یا فقط در عملکردهای انتخابی، غیرفعال کنید.

Cython همچنین به شما امکان می دهد به طور بومی به ساختارهای Python دسترسی پیدا کنید که از پروتکل بافر برای دسترسی مستقیم به داده های ذخیره شده در حافظه (بدون کپی برداری میانی). نمایش‌های حافظه Cython به شما امکان می‌دهد با آن ساختارها با سرعت بالا کار کنید، و با سطح ایمنی متناسب با کار. برای مثال، داده‌های خام زیر یک رشته پایتون را می‌توان به این شکل (سریع) بدون نیاز به گذراندن زمان اجرای پایتون (آهسته) خواند.

کد Cython C می تواند از انتشار GIL بهره مند شود

قفل مترجم جهانی پایتون یا GIL، رشته‌های درون مفسر را همگام‌سازی می‌کند، از دسترسی به اشیاء پایتون محافظت می‌کند و مناقشات را برای منابع مدیریت می‌کند. اما GIL به‌طور گسترده مورد انتقاد قرار گرفته است به‌عنوان سدی برای پایتون با عملکرد بهتر، به‌ویژه در سیستم‌های چند هسته‌ای.

اگر بخشی از کد دارید که هیچ ارجاعی به اشیاء پایتون ندارد و عملیات طولانی‌مدت انجام می‌دهد، می‌توانید آن را با دستور  with nogil: علامت‌گذاری کنید تا اجازه دهید بدون GIL اجرا شود. . این کار مفسر پایتون را برای انجام کارهای دیگر در این مدت آزاد می‌کند و به کد Cython اجازه می‌دهد از چندین هسته (با کار اضافی) استفاده کند.

از Cython می توان برای پنهان کردن کدهای حساس پایتون استفاده کرد

دکامپایل و بازرسی ماژول‌های پایتون بسیار آسان است، اما باینری‌های کامپایل شده اینطور نیستند. هنگام توزیع یک برنامه پایتون برای کاربران نهایی، اگر می‌خواهید برخی از ماژول‌های آن را در برابر جاسوسی‌های معمولی محافظت کنید، می‌توانید این کار را با کامپایل کردن آنها با Cython انجام دهید.

با این وجود، توجه داشته باشید که چنین مبهم‌سازی عوارض جانبی قابلیت‌های Cython است، نه یکی از عملکردهای مورد نظر آن. همچنین، اگر یک باینری اختصاص داده شده یا به اندازه کافی تعیین شده باشد، غیرممکن نیست که یک باینری را دکامپایل یا مهندسی معکوس کنید. و، به عنوان یک قاعده کلی، اسرار، مانند نشانه‌ها یا سایر اطلاعات حساس، هیچوقت نباید در باینری‌ها پنهان شوند—اغلب به راحتی می‌توان با استفاده از یک روکش هگزا ساده نقاب زد.

شما می توانید ماژول های کامپایل شده توسط Cython را مجدداً توزیع کنید

اگر در حال ساختن یک بسته پایتون هستید تا به دیگران توزیع شود، چه به صورت داخلی یا از طریق PyPI، اجزای کامپایل شده توسط Cython را می توان با آن اضافه کرد. این مؤلفه‌ها را می‌توان برای معماری‌های خاص ماشین از پیش کامپایل کرد، اگرچه شما باید چرخ‌های پایتون جداگانه‌ای برای هر معماری بسازید. در صورت عدم موفقیت، کاربر می‌تواند کد Cython را به عنوان بخشی از فرآیند راه‌اندازی کامپایل کند، تا زمانی که یک کامپایلر C در دستگاه مقصد موجود باشد.

محدودیت های Cython

به خاطر داشته باشید که Cython یک عصای جادویی نیست. این به طور خودکار هر نمونه از کدهای پوکی پایتون را به کد C بسیار سریع تبدیل نمی کند. برای استفاده بیشتر از Cython، باید از آن عاقلانه استفاده کنید—و محدودیت های آن را درک کنید.

حداقل سرعت برای کدهای پایتون معمولی

وقتی Cython با کد پایتون روبرو می‌شود، نمی‌تواند به طور کامل به C ترجمه شود، آن کد را به یک سری فراخوانی C به قسمت‌های داخلی پایتون تبدیل می‌کند. این به معنای خارج کردن مفسر پایتون از حلقه اجرا است که به طور پیش‌فرض به کد سرعت متوسطی بین ۱۵ تا ۲۰ درصد می‌دهد. توجه داشته باشید که این بهترین سناریو است. در برخی شرایط، ممکن است شاهد بهبود عملکرد یا حتی کاهش عملکرد نباشید. عملکرد را قبل و بعد اندازه گیری کنید تا مشخص شود چه چیزی تغییر کرده است.

سرعت کمی برای ساختارهای داده بومی پایتون

پایتون مجموعه‌ای از ساختارهای داده مانند رشته‌ها، فهرست‌ها، تاپل‌ها، دیکشنری‌ها و غیره را فراهم می‌کند. آنها برای توسعه دهندگان بسیار راحت هستند و مدیریت حافظه خودکار خود را دارند. اما آنها کندتر از C خالص هستند.

Cython به شما امکان می دهد به استفاده از تمام ساختارهای داده پایتون ادامه دهید، اگرچه بدون افزایش سرعت. این دوباره به این دلیل است که Cython به سادگی C API های موجود در زمان اجرا پایتون را فراخوانی می کند که آن اشیاء را ایجاد و دستکاری می کنند. بنابراین ساختارهای داده پایتون به طور کلی بسیار شبیه به کد پایتون بهینه سازی شده توسط Cython عمل می کنند: شما گاهی اوقات تقویت می کنید، اما فقط کمی. برای بهترین نتایج، از متغیرها و ساختارهای C استفاده کنید. خبر خوب این است که Cython کار با آنها را آسان می کند.

کد Cython در “C خالص” سریعترین اجرا می شود

اگر تابعی در C داشته باشید که با کلمه کلیدی cdef برچسب گذاری شده است، با همه متغیرهای آن و تابع درون خطی آن به چیزهایی که C خالص هستند فراخوانی می کند، با حداکثر سرعت C اجرا می شود. اما اگر آن تابع به هر کد بومی پایتون، مانند ساختار داده پایتون یا فراخوانی به یک API داخلی پایتون اشاره کند، آن فراخوانی یک گلوگاه عملکرد خواهد بود.

خوشبختانه، Cython راهی برای شناسایی این تنگناها ارائه می دهد: یک کد منبع  گزارش  که در یک نگاه نشان می دهد که کدام بخش از برنامه Cython شما C خالص است و کدام بخش با پایتون تعامل دارد. هرچه برنامه بهتر بهینه شود، تعامل کمتری با پایتون خواهد داشت.

cython report

گزارش کد منبع تولید شده برای یک برنامه Cython. نواحی سفید رنگ C خالص هستند. نواحی زرد نشان دهنده تعامل با اجزای داخلی پایتون هستند. یک برنامه Cython که به خوبی بهینه شده است تا حد امکان رنگ زرد کمتری خواهد داشت. آخرین خط گسترش یافته کد C را در زیر کد Cython متناظر آن نشان می دهد. خط ۸ به دلیل کد رسیدگی به خطا که Cython به طور پیش‌فرض برای تقسیم می‌سازد، زرد است، اگرچه می‌توان آن را غیرفعال کرد.

Cython و NumPy 

Cython استفاده از کتابخانه‌های شخص ثالث مبتنی بر C مانند NumPy را بهبود می‌بخشد. از آنجایی که کد Cython در C کامپایل می شود، می تواند مستقیماً با آن کتابخانه ها تعامل داشته باشد و تنگناهای پایتون را از حلقه خارج کند.

اما NumPy، به ویژه، با Cython به خوبی کار می کند. Cython از ساختارهای خاص در NumPy پشتیبانی می کند و دسترسی سریع به آرایه های NumPy را فراهم می کند. و همان نحو آشنای NumPy که در یک اسکریپت پایتون معمولی استفاده می‌کنید، می‌تواند در Cython همانطور که هست استفاده شود.

با این حال، اگر می‌خواهید نزدیک‌ترین پیوندهای ممکن را بین Cython و NumPy ایجاد کنید، باید کد را با نحو سفارشی Cython تزئین کنید. برای مثال، عبارت cimport به کد Cython اجازه می‌دهد تا ساختارهای سطح C را در کتابخانه‌ها در زمان کامپایل برای سریع‌ترین اتصال‌های ممکن ببیند.

از آنجایی که NumPy بسیار مورد استفاده قرار می گیرد، Cython NumPy را پشتیبانی می کند “بیرون جعبه.” اگر NumPy را نصب کرده‌اید، فقط می‌توانید cimport numpy را در کد خود بیان کنید، سپس تزیینات بیشتر را اضافه کنید برای استفاده از عملکردهای در معرض دید.

نمایه و عملکرد Cython

شما بهترین عملکرد را از هر کدی با نمایه کردن آن و مشاهده مستقیم نقاط گلوگاه دریافت می کنید. Cython قلاب‌هایی را برای ماژول cProfile پایتون فراهم می‌کند، بنابراین می‌توانید از < a>ابزارهای پروفایل خود پایتون، مانند cProfile، برای مشاهده عملکرد کد Cython شما. (ما همچنین به ابزار داخلی خود Cython برای فهمیدن اینکه کد شما چقدر کارآمد به C ترجمه می شود اشاره کردیم.)

به یاد داشته باشید که در همه موارد Cython جادویی نیست—روش‌های عملکرد معقول در دنیای واقعی همچنان اعمال می‌شوند. هرچه کمتر بین Python و Cython رفت و آمد کنید، برنامه شما سریعتر اجرا می شود.

به عنوان مثال، اگر مجموعه‌ای از اشیاء دارید که می‌خواهید در Cython پردازش کنید، آن را در پایتون تکرار نکنید و در هر مرحله یک تابع Cython را فراخوانی کنید. کل مجموعه را به ماژول Cython خود منتقل کنید و در آنجا تکرار کنید. این تکنیک اغلب در کتابخانه‌هایی استفاده می‌شود که داده‌ها را مدیریت می‌کنند، بنابراین مدل خوبی برای شبیه‌سازی در کد شماست.

ما از Python استفاده می‌کنیم زیرا برنامه‌نویس را راحت می‌کند و توسعه سریع را امکان‌پذیر می‌کند. گاهی اوقات بهره وری برنامه نویس به قیمت عملکرد تمام می شود. با Cython، فقط کمی تلاش بیشتر می تواند بهترین های هر دو دنیا را به شما بدهد.