مهدی
مهدی
خواندن ۹ دقیقه·۳ سال پیش

پایتون؛ زبانی برای شرکت های مشتاق پیشرفت و performance بالا


چند روز پیش داشتم مقاله های اقای Brett Cannon، یکی از توسعه‌دهنده های core پایتون، رو میخوندم که رسیدم به این مقاله:

https://snarky.ca/programming-language-selection-is-a-form-of-premature-optimization/

بخش هاییش رو براتون اینجا میگم.

«تا حالا شنیدید که میگن پایتون برای فلان پروژه خوب نیست چون نمیتونه به اندازه کافی سریع باشه؟ من شنیدم. و این جمله کمی ناامید کننده‌س چون که بانک‌های بزرگ، یوتوب، اینستاگرام، و خییلی جاهای دیگه که نسبت به performance حساس هستن هنوز هم میان پایتون رو انتخاب میکنن و از این قضیه راضی هم هستن.»

خودمون هم تا حالا بسیاار این بحث هارو شنیدیم و خوندیم و حتی خودمون هم توش شرکت کردیم که: پایتون کُنده، پایتون سرعتش خیلی کمه نرید سمتش. خوب نیست. کدوم شرکتی سرعت نمیخواد.
یا حتی یک سری‌ها پا رو فراتر میگذارن که پایتون خیلی ساده‌ست و در و پیکر نداره. شما یک سری کد رو حفظ میکنید و همون هارو تکرار میکنید. شما اصلا مهارت حل مسئله یاد نمی‌گیرید و از این جور صحبت ها.
یک سری‌ها هم میگن: نه شما حتما باید در شروع برنامه‌نویسی از یک زبانی مثل سی/سی‌پلاس‌پلاس شروع کنید تا عمق پیدا کنید ? (که خودمون هم می‌دونیم ۱. عمق پیدا کردن با چهار روز آموزش دیدن از اون زبان حاصل نمیشه. ۲. همون چهار روز آموزش دیدن، تازه ضرر هم می‌زنه به طرف چون زبان های مختلف، طبیعت های متفاوتی دارن و نوع تفکر در اون ها بالاخره با باقی زبان ها فرق میکنه و ۳. چرا دانشگاه های تاپ آمریکا (مثل MIT) از قول گویدو ون راسم دارن با پایتون شروع میکنن؟!)
بعضی‌های دیگه هم (از امثال مدحج) میگن نه بین فلان زبان و پایتون اون یکی بهتره. چرا؟ چون سوئیچ کردن از پایتون به دیگر زبان‌ها سخته‌ (که باید ازشون سوال پرسید اولا این جمله چرا باید درست باشه؟ و ثانیا مگه ما زبان یاد میگیریم که بعدش حتما سوئیچ کنیم روی یه چیز دیگه؟ ?)

اما سوال اینجاست که آیا واقعا این صحبت ها درستن؟ چندتامون (بعضاً) بخاطر این صحبت ها از پایتون ناامید شدیم و می‌خواستم بریم سراغ چیز دیگه‌ای؟ (یا حتی رفتیم؟!)

از نظر آقای Brett Cannon: «اگر با توجه به تصوراتی که از قبل راجع به یک زبان دارید، که مثلا بگید فلان کنده این یکی سریع عه، یا با توجه به چیزی که بقیه میگن بیاید زبون برنامه نویسی رو انتخاب بکنید برای یک پروژه، این شانس رو از دست میدید که مثلا تیم شما همشون با فلان زبان راحت هستن، همه هم اون زبان رو به خوبی بلد و می‌تونن به خوبی اون مسئله رو حل کنن، اما شما تیم رو مجبور کردی که که با یک زبان سخت تر کار رو انجام بدن که همه میگن اون زبون سریع تره. اماااا شما وقت زیاد و هزینه گزافی رو سر این کار تلف کردید در صورتی که نیازی به این کار نبوده.»
ایشون هم در این مقاله و هم در توییت‌شون: راجع به یک چرخه صحبت می‌کنن، که میگن روند کار ما اینجوریه:
۱. با پایتون بنویسش
۲. کد رو profile کن
۳. _اگررر_ به اندازه کافی سریع نبود، الگوریتم و داده‌ساختار هات رو بهتر کن
۴. باز هم _اگررر_ به اندازه کافی سریع نبود، از implementation دیگری از پایتون استفاده کن (اونی که زیاد نخواد کد رو تغییر بدی (مثلا Pyjion که اصلا لازم نیست به کد دست بزنی :)))
۵. _اگررر_باززز_ هم به اندازه کافی سریع نبود:‌ کدتون پایتون‌تان را با پیوند زدن کد زبان های دیگر بهینه کنید. (language bindings)
در حالی که ممکنه این قدم ها طولانی به نظر بیاد، یادتون باشه که پایتون به سمت بهروی (productivity) گرایش زیادی داره، و همین باعث شکایت های زیادی شده ? که یک نفر کاری را در یک سوم زمانی که تیم مقابلش همان چیز را با جاوا یا سی‌پلاس‌پلاس نوشته، انجام داده. و زمانی که اون تیم ورژن یک اون چیز رو تا مرحله بتا پیش برده، نسخه‌ای که با پایتون نوشته شده به ورژن ۳ رفته و کلی روی اون تست انجام شده و به بلوغ خوبی رسیده، و به اندازه کافی بهینه شده که اون نیاز رو به خوبی برطرف کنه.

یک مثال کوچولو ببینیم:
خب به شما گفته شده تعدادی از اعداد دنباله فیبوناچی رو چاپ کنید. چیزی که اول از همه به ذهن میرسه خوب فرمول بازگشتی دنباله هست:

fib(n) = fib(n - 1) + fib(n - 2) where n = 1 -> 0 and n = 2 -> 1

https://en.wikipedia.org/wiki/Fibonacci_number

خب میای و می‌نویسی

تا چندین شماره اول اصلا حس نمیشه که این کنده. و مشکل شما حل شده و براتون کافی بوده.
اما میره جلو تر و می‌بینی واقعا دیگه این کنده :(
اگه این مقاله رو نخونده باشی، و صحبت های دیگران روت تاثیر گذاشته باشن : میگی نه این پایتون به درد نمی‌خوره!

اما بیا طبق اون چرخه‌ای که معرفی شد پیش بریم:
خب یکی از راه‌حل هایی که همین الگوریتم بازگشتی مارو سریع تر میکنه cache کردن، مقادیر محاسبه شده‌ست:

این خودش سرعت رو خیلی بهبود می‌بخشه و می‌تونه آخرین قدم ما باشه و برامون کافی باشه.اما میگید نه، باز هم کنده
بیاید الگوریتم رو عوض کنیم:

یهو می‌بینی بووووم!! چقدر خوب بود سرعت واقعا زیاد شد. فهمش هم چقدر راحت تر از اون تابع بازگشتی شده!! این یک مثال ساده بود اما خییلی جا های دیگه میشه دیدش.

بیاید چند تا از قدم هایی که ایشون به ما گفتن رو بررسی کنیم:

  • با پایتون شروع کنید.

شما بالاخره باید از یجایی شروع کنید و به لطف پایتون که طوری طراحی شده که to make you productive، شما خیلی راحت می‌تونید یه چیزی رو بنویسید و خیلی سریع به نتیجه برسید و احتمالش هم بالا هست که که اون کد به اندازه کافی سریع عه و دیگه کار شما تموم شده :))

به قول آقای Andrew Godwin، نویسنده Django Channels و یکی از توسعه‌دهنده هایی که داره جنگو رو async میکنه: «خود من تا جایی که بتونم سمت async نمیرم و کار خودم و بقیه رو پیچیده نمیکنم.» خب این یعنی چی. ایشون ادامه می‌دادن، برای خیلی از شما همین جنگوعه Sync کافیه، و بعضی جاها شاید مجبور باشید که کد async بنویسید.

این یعنی واقعا خیلی وقت ها اون‌ تصوری که ما از کندی داریم و اینکه مثلا باید این کد حتما حتما زیر نیم ثانیه ران بشه غلطه. یکی از مدرسان پایتون، آقای Tim Buchalka، توی دوره‌ای که برای پایتون دارن یه مثال قشنگی رو میزنن.
ایشون میگن این کد رو ببینید:

بیاید فرض کنیم کد بالا قرار توی دو میلی ثانیه انجام میشه،و کد زیر

فرض کنیم توی یک میلی ثانیه انجام میشه.خب فرقش چیه؟
گفت: فرض کنید کد شما داره توی یک جت جنگنده اجرا میشه و اون جت در هر ثانیه ۲۳۰۰ متر میره.خب هر میلی ثانیه برای این جت چند متر میشه؟ میشه ۲.۳ متر. اینجاست که مهم میشه از کد پایینی استفاده کنیم تا کد بالایی :)

  • الگوریتم و داده‌ساختارتون رو بهتر کنید.

این کد رو ببینید:


خب فرقش چیه؟ نتایج که فرقی نمی‌کنه!!!!
اماااا خیلی فرق میکنه: در واقع membership testing در داده ساختار هایی مثل list و tuple (که ایندکس دارن و ترتیب دارن) به صورتی خطی هست، یعنی پایتون یکی یکی به ترتیب ازشون آیتم میگیره و چک میکنه. امااا در چیزی مثل set، این قضیه برقرار نیست و چون از hash map ها استفاده شده خیییلی سریع تر در اون المنت ها پیدا میشن.

فرقی بین چک کردن ۱ توی لیست و ست نیست، مثلا میشه 0.0001 ثانیه، اما تفاوت جایی هست که میای یه آیتم از آخر رو می‌خوای چک کنی. در این مثال برای لیست مثلا میشه 0.01 ثانیه اما برای ست آچنان فرقی نمیکنه و تقریبا همون 0.0001 بدست میاد.
دیدید! اینجاست که لینوس توزوالدز میگه:

"Bad programmers worry about the code. Good programmers worry about data structures and their relationships."
  • از یک پیاده‌سازی (implementation) دیگه پایتون استفاده کنید.

خیلی هامون راجع به مفسر های دیگه‌ای که دارن توسعه داده میشن خبر داریم. اون‌هایی که هر کدوم با هدف خاصی نوشته میشن و دارن پیشرفت میکنن (هر چند بعضی‌ها شون دیگه راکد شدن.)
چیزی که مهمه اینکه مفسری رو بر ندارید که بخواید کدتون رو خییلی عوض کنید. چیزی رو انتخاب کنید که کمتر زحمت رو براتون داشته باشه. برای مثال:

۱. PyPy
۲. Numba
۳. mypyc
۴. Pyjion

باز هم مثل قبل شما باید چند تا بررسی کنید و بهترین رو انتخاب کنید (برای مثال اگه منطقه های داغ کد شما (اونجایی که زمان زیادی میگیره) با اعداد سر و کار ندارن Numba گزینه خوبی نخواهد بود.)

  • از Language Binding ها استفاده کنید.
به قول ایشون: «این پست به دلیل توییتی در مورد استفاده از Rust برای سرعت بخشیدن به برخی از کدهای پایتون نوشته شد. به لطف تاریخچه طولانی پایتون، که می‌تونی خیلی راحت به دیگر زبان ها وصل بشه یا دیگر زبان ها بهش وصل بشن (glue code)، راه های بی شماری برای فراخوانی زبان های دیگر که ممکن است بتوانند سریعتر از خود پایتون کار کنند، به دست آمده است»:

۱. PyO3 for Rust
۲. HPy for C
۳. pybind11 for C++
۴. Cython for C
...

و این یک لیست بلند بالایی واسه دیگر زبان ها و ابزار هاست.
تفاوت اصلی این روش با روش قبلی اینکه، اینجا دیگه شما باید کمی کد نویسی کنید. اما اگر نیاز های performance شما دیگه خییییلی مهم و حیاتی عه، این گزینه راحت میتونه نیاز شما رو برطرف کنه. می‌تونید این راه رو شروع کنید، و شاید آرام آرام در طول یک زمانی ببینید کل کد های پایتونی خود یا عوض کرده‌اید اما مهم‌ترین مسئله‌ای که وجود داره اینکه:
«الگوریتم ها و طراحی‌های اون برنامه رو قبلا تایید کردید و ابزارهای زیادی هم برای تست اون کد دارید که می‌تونید استفاده کنید.»
یکی از مهم‌ترین بند هایی که در این مقاله هست اینه:

Consider optimizing for developer time, not computation costs.
به قول ایشون: «اینایی که زبان برنامه نویسی رو براساس پرفورمنسش انتخاب میکنن، معمولا اونایی هستن که فکر میکنن هزینه سرور و اینا از بهینه کردن زمان توسعه مهمتره. ولی با توجه به این که هزینه برنامه نویس ها گرونه، من (نویسنده) فک نمیکنم همیشه اینجوری باشه.
اگر شما به هزینه سرویس ابری‌تون نگاه کنید و بعد به تعداد برنامه نویس هایی که دارین حقوق میدید توجه کنید، حدس من اینه که اگر زبانی رو انتخاب کنید که به تعداد برنامه نویس های کمتری نیاز داشته باشه، شما میتونید با کاهش هزینه حقوق برنامه نویس ها، بیشتر از کاهش هزینه سرور، در هزینه هاتون صرفه جویی کنید.»
از قول یه دوست: «اگه وقت کامپیوتر ها برای شما از وقت برنامه‌نویس ها ارزش‌مند تره برید هم پول بیشتری به برنامه‌نویس هاتون بدید و هم وقت خیلی بیشتری رو صرف توسعه یک کاری بکنید. اما در دنیای واقعی مسئله اینکه وقت برنامه نویس چقدر گرفته میشه خیلی مهم تره، اگه برید قیمت هارو مقایسه کنید می‌بینید که بالاتر بردن قیمت سرویس ابری خیییلی به صرفه‌تر از پول بیشتر دادن به برنامه نویس عه.»
نقل قول از یه دوست دیگه:‌«نمونه چنین چیزی شرکت shopify هست که استکش روبی هست. روبی هم مثل پایتون ساده هست ولی ملت میگن اسکیل نمیشه، هزینه سرورش بالاس و اگر شرکتها به جای انتخابش از زبون های بهینه تر مثل جاوا، سی یا گو استفاده کنن، میتونن در هزینه سرور و در نهایت هزینه های شرکت صرفه جویی کنن. ولی خب این شرکت دقیقا نشون داده که این هزینه سرور اونچنان که ادعا میشه زیاد نیست و از هزینه و وقت برنامه نویس ارزش کمتری داره. در نتیجه هزینه سرورهای این شرکت در مقابل درامدش تقریبا هیچه.»




همه این حرفا به این معنی عه که برای بعضی شرکت‌ها براشون کم کردن هر cycle عه cpu واقعا ارزش داره،‌ اما خب اون‌ها در عمل دارن دیتاسنتر می‌نویسن. اما شمایی که اصلا کاری به این چیز نداری، یه چرتکه بندازید ببینید پایین آوردن productivity برنامه‌نویس های تیم‌تون، به اسم اینکه «خب قراره سریع تر محاسبات انجام بشه،» به صرفه‌تره یا اینکه بیاید چهار تا سرور به سرور هاتون اضافه کنید؟

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