مسیر تایپ کردن 4 میلیون خط کد پایتون. قسمت 2

امروز ما در حال انتشار بخش دوم از ترجمه مطالب در مورد نحوه سازماندهی کنترل نوع چندین میلیون خط کد پایتون در Dropbox هستیم.

مسیر تایپ کردن 4 میلیون خط کد پایتون. قسمت 2

قسمت اول را بخوانید

پشتیبانی از نوع رسمی (PEP 484)

ما اولین آزمایش های جدی خود را با mypy در Dropbox در هفته هک 2014 انجام دادیم. Hack Week یک رویداد یک هفته ای است که توسط Dropbox میزبانی می شود. در این مدت کارمندان می توانند روی هر چیزی که می خواهند کار کنند! برخی از معروف ترین پروژه های فناوری Dropbox در رویدادهایی مانند این آغاز شد. در نتیجه این آزمایش، به این نتیجه رسیدیم که mypy امیدوارکننده به نظر می رسد، اگرچه این پروژه هنوز برای استفاده گسترده آماده نیست.

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

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

نحو نوع اشاره ای که در نهایت پذیرفته شد بسیار شبیه به آنچه mypy در آن زمان پشتیبانی می کرد بود. PEP 484 با پایتون 3.5 در سال 2015 منتشر شد. پایتون دیگر یک زبان تایپ پویا نبود. من دوست دارم به این رویداد به عنوان یک نقطه عطف مهم در تاریخ پایتون فکر کنم.

شروع مهاجرت

در پایان سال 2015، Dropbox تیمی متشکل از سه نفر را برای کار بر روی mypy ایجاد کرد. آنها شامل گویدو ون روسوم، گرگ پرایس و دیوید فیشر بودند. از آن لحظه به بعد، اوضاع به سرعت شروع به توسعه کرد. اولین مانع برای رشد mypy عملکرد بود. همانطور که در بالا اشاره کردم، در روزهای اولیه پروژه به ترجمه پیاده سازی mypy به C فکر می کردم، اما این ایده فعلا از لیست خارج شد. ما در اجرای سیستم با استفاده از مفسر CPython گیر کردیم، که برای ابزارهایی مانند mypy به اندازه کافی سریع نیست. (پروژه PyPy، یک پیاده سازی جایگزین پایتون با یک کامپایلر JIT نیز به ما کمک نکرد.)

خوشبختانه، برخی از پیشرفت های الگوریتمی در اینجا به کمک ما آمده است. اولین "شتاب دهنده" قدرتمند، اجرای بررسی افزایشی بود. ایده پشت این بهبود ساده بود: اگر همه وابستگی‌های ماژول نسبت به اجرای قبلی mypy تغییر نکرده باشند، می‌توانیم در حین کار با وابستگی‌ها از داده‌های ذخیره‌شده در طول اجرای قبلی استفاده کنیم. ما فقط نیاز به بررسی نوع فایل‌های اصلاح‌شده و فایل‌های وابسته به آن‌ها داشتیم. Mypy حتی کمی فراتر رفت: اگر رابط خارجی یک ماژول تغییر نمی کرد، mypy فرض می کرد که ماژول های دیگری که این ماژول را وارد کرده اند نیازی به بررسی مجدد ندارند.

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

این دوره پذیرش سریع و طبیعی بررسی نوع در Dropbox بود. تا پایان سال 2016، تقریباً 420000 خط کد پایتون با حاشیه نویسی نوع داشتیم. بسیاری از کاربران مشتاق بررسی تایپ بودند. تیم های توسعه بیشتر و بیشتری از Dropbox mypy استفاده می کردند.

آن موقع همه چیز خوب به نظر می رسید، اما هنوز کارهای زیادی برای انجام دادن داشتیم. ما شروع به انجام نظرسنجی‌های دوره‌ای کاربران داخلی کردیم تا مناطق مشکل‌دار پروژه را شناسایی کنیم و بفهمیم چه مسائلی باید ابتدا حل شوند (این روش هنوز در شرکت امروزی استفاده می‌شود). همانطور که مشخص شد مهمترین آنها دو وظیفه بود. اول، ما به پوشش بیشتر نوع کد نیاز داشتیم، دوم، ما به mypy نیاز داشتیم تا سریعتر کار کند. کاملاً واضح بود که کار ما برای سرعت بخشیدن به mypy و پیاده سازی آن در پروژه های شرکت هنوز تا کامل شدن فاصله دارد. ما با آگاهی کامل از اهمیت این دو کار، دست به کار شدیم تا آنها را حل کنیم.

بهره وری بیشتر!

بررسی های افزایشی mypy را سریعتر کرد، اما ابزار هنوز به اندازه کافی سریع نبود. بسیاری از بررسی های افزایشی حدود یک دقیقه به طول انجامید. دلیل این امر واردات دوره ای بود. این احتمالاً کسی را که با پایگاه های کد بزرگ نوشته شده در پایتون کار کرده است شگفت زده نخواهد کرد. ما مجموعه‌ای از صدها ماژول داشتیم که هر کدام به‌طور غیرمستقیم بقیه را وارد می‌کردند. اگر هر فایلی در یک حلقه واردات تغییر می کرد، mypy باید تمام فایل های موجود در آن حلقه و اغلب ماژول هایی را که ماژول ها را از آن حلقه وارد می کردند، پردازش می کرد. یکی از این چرخه‌ها «درهم وابستگی» بدنام بود که دردسرهای زیادی را در Dropbox ایجاد کرد. زمانی که این ساختار شامل صدها ماژول بود، در حالی که به طور مستقیم یا غیرمستقیم آزمایشات زیادی وارد شد، در کد تولید نیز استفاده شد.

ما امکان «گشودن» وابستگی های دایره ای را در نظر گرفتیم، اما منابع لازم برای انجام آن را نداشتیم. کد زیادی وجود داشت که ما با آن آشنا نبودیم. در نتیجه، ما به یک رویکرد جایگزین رسیدیم. ما تصمیم گرفتیم که mypy را حتی در صورت وجود "درهم و برهم وابستگی" به سرعت کار کنیم. ما با استفاده از دیمون mypy به این هدف رسیدیم. دیمون یک فرآیند سرور است که دو قابلیت جالب را پیاده سازی می کند. اولا، اطلاعات مربوط به کل پایه کد را در حافظه ذخیره می کند. این بدان معناست که هر بار که mypy را اجرا می‌کنید، لازم نیست داده‌های کش مربوط به هزاران وابستگی وارد شده را بارگیری کنید. ثانیاً، او با دقت، در سطح واحدهای ساختاری کوچک، وابستگی‌های بین توابع و موجودیت‌های دیگر را تحلیل می‌کند. به عنوان مثال، اگر تابع foo یک تابع را فراخوانی می کند bar، سپس یک وابستگی وجود دارد foo از bar. هنگامی که یک فایل تغییر می کند، دیمون ابتدا به صورت مجزا، فقط فایل تغییر یافته را پردازش می کند. سپس به تغییرات قابل مشاهده خارجی آن فایل، مانند امضای تابع تغییر یافته نگاه می کند. دیمون از اطلاعات دقیق در مورد واردات فقط برای بررسی مجدد آن دسته از توابعی استفاده می کند که در واقع از تابع تغییر یافته استفاده می کنند. به طور معمول، با این رویکرد، شما باید تعداد کمی از توابع را بررسی کنید.

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

حتی بهره وری بیشتر!

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

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

تمرکز بر API افزونه CPython برای از دست ندادن قابلیت‌های مدیریت پروژه کلیدی بود. ما نیازی به پیاده‌سازی ماشین مجازی یا کتابخانه‌های مورد نیاز mypy نداشتیم. علاوه بر این، ما همچنان به کل اکوسیستم پایتون و همه ابزارها (مانند pytest) دسترسی خواهیم داشت. این بدان معناست که ما می‌توانیم به استفاده از کد پایتون تفسیر شده در طول توسعه ادامه دهیم و به ما امکان می‌دهد به جای اینکه منتظر کامپایل شدن کد باشیم، با الگوی بسیار سریعی برای ایجاد تغییرات کد و آزمایش آن کار کنیم. به نظر می رسید که به اصطلاح روی دو صندلی نشسته بودیم و این کار را دوست داشتیم.

کامپایلری که ما آن را mypyc نامیدیم (از آنجایی که از mypy به عنوان جلویی برای تجزیه و تحلیل انواع استفاده می‌کند)، پروژه بسیار موفقی بود. به طور کلی، ما تقریباً 4 برابر سرعت برای اجرای مکرر mypy بدون حافظه پنهان به دست آوردیم. توسعه هسته پروژه mypyc تیم کوچکی از مایکل سالیوان، ایوان لوکیفسکی، هیو هان و من حدود 4 ماه تقویمی طول کشید. این مقدار کار بسیار کمتر از آن چیزی بود که برای بازنویسی mypy، به عنوان مثال، در C++ یا Go لازم بود. و ما مجبور شدیم تغییرات بسیار کمتری را در پروژه ایجاد کنیم تا زمانی که آن را به زبان دیگری بازنویسی کنیم. ما همچنین امیدوار بودیم که بتوانیم mypyc را به چنان سطحی برسانیم که سایر برنامه نویسان Dropbox بتوانند از آن برای کامپایل و سرعت بخشیدن به کد خود استفاده کنند.

برای دستیابی به این سطح از عملکرد، مجبور شدیم راه حل های مهندسی جالبی را اعمال کنیم. بنابراین، کامپایلر می‌تواند بسیاری از عملیات را با استفاده از ساختارهای سریع و سطح پایین C افزایش دهد. برای مثال، یک فراخوانی تابع کامپایل شده به فراخوانی تابع C ترجمه می‌شود. و چنین فراخوانی بسیار سریعتر از فراخوانی یک تابع تفسیر شده است. برخی عملیات‌ها، مانند جستجوی فرهنگ لغت، همچنان شامل استفاده از تماس‌های معمولی C-API از CPython می‌شدند، که در زمان کامپایل فقط کمی سریع‌تر بودند. ما توانستیم بار اضافی روی سیستم را که توسط تفسیر ایجاد شده بود حذف کنیم، اما این در این مورد فقط از نظر عملکرد سود کمی داشت.

برای شناسایی رایج‌ترین عملیات «آهسته»، پروفایل کد را انجام دادیم. با استفاده از این داده‌ها، سعی کردیم یا mypyc را تغییر دهیم تا کد C سریع‌تری برای چنین عملیاتی تولید کند، یا کد پایتون مربوطه را با استفاده از عملیات سریع‌تر بازنویسی کنیم (و گاهی اوقات ما به سادگی راه‌حل کافی برای آن یا مشکل دیگر نداشتیم) . بازنویسی کد پایتون اغلب راه حل ساده‌تری برای حل مشکل بود تا اینکه کامپایلر به طور خودکار همان تبدیل را انجام دهد. در درازمدت، ما می‌خواستیم بسیاری از این تحولات را خودکار کنیم، اما در آن زمان روی افزایش سرعت mypy با حداقل تلاش متمرکز بودیم. و در حرکت به سمت این هدف چندین گوشه را بریدیم.

ادامه ...

خوانندگان عزیز! وقتی از وجود پروژه mypy مطلع شدید، برداشت شما از پروژه mypy چه بود؟

مسیر تایپ کردن 4 میلیون خط کد پایتون. قسمت 2
مسیر تایپ کردن 4 میلیون خط کد پایتون. قسمت 2

منبع: www.habr.com

اضافه کردن نظر