عملکرد پایتون: حلقههای نیتیو نامپای، فراخوانها و هزینههای تبدیل
یک تست عددی کنترلشده تنها با CPU، مقادیر یکسان را از طریق حلقههای CPython، محاسبات نامپای، یک پوشش فراخوان پایتون و یک حلقه کامپایلشده نامبا پیگیری میکند.
یک تست عددی کنترلشده تنها با CPU، مقادیر یکسان را از طریق حلقههای CPython، محاسبات نامپای، یک پوشش فراخوان پایتون و یک حلقه کامپایلشده نامبا پیگیری میکند. نامپای با ورودی آماده پیشتاز بزرگی داشت، در حالی که تبدیل از و به لیستهای پایتون این مزیت را به نزدیک برابری کاهش داد. این قسمت مرز اجرا و نحوه اندازهگیری نتیجه مورد نیاز یک برنامه را توضیح میدهد.
نسخه نوشتاری را بخوانید (انگلیسی) ↗
این ویدیو چه مواردی را پوشش میدهد
- CPython کد منبع را به بایتکد کامپایل میکند. کار مکرر مفسر/شیء و یک حلقه عددی نیتیو تایپشده، ترتیبهای اجرایی متفاوتی هستند.
- محاسبات عددی نامپای میتواند دستههای تایپشده را به عملیات کامپایلشده بسپارد؛ حلقه محاسباتی همچنان وجود دارد و نماها میتوانند چیدمانهای غیرمتوالی داشته باشند.
- تبدیل و نمایش خروجی مورد نیاز را اندازهگیری کنید. در این اجرا، تبدیل لیستهای پایتون به آرایهها و بازگشت، مزیت بزرگ آرایه آماده را از بین برد.
- حجمهای کاری کوچک میتوانند پایتون معمولی را ترجیح دهند زیرا هزینههای ثابت فراخوانی آرایه مهم هستند. فهرستفهمی هشت مقداره برنده این مقایسه اندازهگیری شده بود.
- np.vectorize یک پوشش راحتی است که پایتون را برای عناصر متوالی فراخوانی میکند؛ رابط دستهای به تنهایی اجرای عددی نیتیو را برقرار نمیکند.
- حلقههای کامپایلشده با نحو پایتون نیز میتوانند سریع باشند. هزینههای کامپایل/راهاندازی اولیه را هنگام اهمیت برای فراخواننده در نظر بگیرید.
- اجرای نیتیو، CPU SIMD، چندرشتهای و بکاندهای GPU ایدههای جداگانهای هستند. ویژگیهای CPU موجود، دستورالعملهای مورد استفاده در یک اجرای خاص را ردیابی نمیکنند.
رونوشت ترجمه شده
ترجمه شده از روایت اصلی انگلیسی. صوت و زیرنویسهای موجود توسط YouTube کنترل میشوند.
چرا همان CPU میتواند زودتر کار را تمام کند؟
0:00 شما فکر میکنید پایتون برای سریع شدن به یک زبان جدید نیاز دارد. همان CPU دسته ما را سریعتر اجرا کرد بعد از اینکه حلقه جابجا شد. کار به کجا رفت؟ چه زمانی افزایش سرعت از بین میرود؟ کدام میانبر پایتون را بازمیگرداند؟ این Daily Diff است، از زیر کاپوت. آن میانبر را زیر نظر داشته باشید. CPython پیادهسازی استاندارد پایتون است.
0:17 این کد منبع شما را به بایتکد کامپایل میکند، سپس آن دستورالعملها را در یک مفسر اجرا میکند. NumPy یک کتابخانه عددی است که آرایههای آن مقادیر را با یک نوع داده اعلام شده نگه میدارند. محاسبات داخلی آن میتواند یک دسته کامل را به عملیات کامپایل شده بسپارد. یک میلیون مقدار اعشاری را در نظر بگیرید، هر کدام را مربع کنید، سپس مقدار اصلی را اضافه کنید. هر نسخه مقادیر یکسان دریافت میکند و پاسخهای منطبق تولید میکند. مقادیر به اندازهای کوچک هستند که تفاوتهای گرد کردن را از مسابقه خارج کنند.
0:40 در این دستگاه، فهرستفهمی حدود سی و سه میلیثانیه طول کشید. NumPy کمتر از یک میلیثانیه طول کشید. هر دو بر روی CPU اجرا شدند. این مدرک مفیدی برای این حجم کاری است، و یک آگهی ترحیم وحشتناک برای یک زبان برنامهنویسی. با حلقه معمولی شروع کنید.
یک حلقه پایتون چه کاری را تکرار میکند؟
0:53 پایتون مقدار بعدی را میگیرد، محاسبات را انجام میدهد، و پاسخ را اضافه میکند. سپس این کار را دوباره انجام میدهد. ضرب ارزان است. ترتیب دادن ضرب بعدی، و مدیریت اشیاء پایتون در اطراف آن، هر بار کار اضافه میکند. این کد مونتاژ واقعی تابع تست شده است. تکرار، ضرب و جمع به عنوان دستورالعملهای بایتکد ظاهر میشوند. نسخهها و مفسران پایتون انتخابهای متفاوتی انجام میدهند.
1:14 این زمانبندیها به نسخه روی صفحه، که پایتون سه نقطه ده است، تعلق دارند. یک لیست پایتون ارجاعاتی به اشیاء را ذخیره میکند، بنابراین انواع مختلفی از چیزها میتوانند یک لیست را به اشتراک بگذارند. کار محاسباتی ما در سراسر از اعداد استفاده میکند. برای یک ترتیب عمومی پول میدهد در حالی که یک سرویس تکراری درخواست میکند. تصور کنید یک میلیون بار همان قهوه را سفارش میدهید و برای هر فنجان یک فرم جدید پر میکنید. دستگاه قهوه خوب است.
1:36 کاغذبازی خودش برای خود شغلی دست و پا کرده است.
نامپای حلقه را به کجا منتقل میکند؟
1:38 NumPy قرارداد را تغییر میدهد. این آرایه مقادیر اعشاری با اندازه ثابت و با یک نوع اعلام شده را نگه میدارد. آرایه ما پیوسته است، بنابراین مقادیر آن در حافظه کنار هم قرار میگیرند. نماها میتوانند چیدمانهای دیگری داشته باشند. عملیات محاسباتی میداند چه نوع دادهای را دریافت میکند. فراخوانی دسته را به یک عملیات نیتیو میدهد. یک حلقه همچنان مقادیر را در داخل کد کامپایل شده بازدید میکند. این میتواند دانش نوع را در طول آن کار دوباره استفاده کند.
2:00 پایتون همچنان فردی است که کار را تحویل میدهد. ایتامار ترنر تراورینگ تفاوت را در Python Speed توضیح میدهد. رابطهای دستهای، پیادهسازیهای نیتیو و دستورالعملهای برداری پردازنده موضوعات جداگانهای هستند. نام تابع شواهد بسیار کمی ارائه میدهد.
آیا سریع به معنای GPU یا هستههای CPU زیاد است؟
2:13 این نتیجه از NumPy بر روی CPU استفاده کرد. یک بکاند GPU مانند CuPy خانه متفاوتی برای آرایهها و عملیات انتخاب میکند. خرید یک GPU این حلقه معمولی پایتون را در همان جایی که قبلاً اجرا میشد، رها میکند. دستورالعملهای برداری CPU میتوانند چندین مقدار را با هم پردازش کنند. NumPy مکانیزمی برای انتخاب هستههای پشتیبانی شده دارد. زمان اجرا ما ویژگیهای موجود را گزارش میکند، اما دستورالعملهای این عبارت را ردیابی نمیکند. دادن تمام افزایش اندازهگیری شده به دستورالعملهای برداری، شواهد را بیش از حد میفروخت.
2:40 سبد خرید GPU میتواند بسته بماند. چیزی عمیقاً رضایتبخش در مورد یک رفع عملکرد وجود دارد که خرید را نیز لغو میکند. متاسفانه، بنچمارک ادامه مییابد.
چه زمانی افزایش سرعت چشمگیر ناپدید میشود؟
2:49 با هشت مقدار، فهرستفهمی برنده شد. فراخوانی مکانیزم آرایه بیشتر از کار کوچک هزینه داشت. آرایههای آماده بزرگتر پیش افتادند. ما از آزمایشهای مکرر و یک فرآیند گرم شده استفاده کردیم. بیلدهای جدیدتر پایتون میتوانند نقطه عبور را تغییر دهند. سپس آنچه برنامه نیاز دارد را تغییر دهید. با یک لیست پایتون شروع کنید و با یک لیست پایتون تازه تمام کنید. NumPy باید ورودی را به یک آرایه تبدیل کند و پاسخ را دوباره به اشیاء پایتون
3:11 تبدیل کند. اگر برنامه شما به این مراحل نیاز دارد، این مراحل باید در صورتحساب باشند. در یک میلیون مقدار، این مسیر کامل حدود بیست و هشت میلیثانیه طول کشید. فهرستفهمی نیز حدود بیست و هشت میلیثانیه طول کشید. پیشتاز چشمگیر ما به یک تساوی نزدیک تبدیل شد. قسمت سریع واقعی بود. مزیت به این بستگی داشت که دادهها در شکل عددی خود باقی بمانند.
کدام مرزها به بنچمارک تعلق دارند؟
3:28 دادهها را دنبال کنید. اگر عملیات بعدی یک آرایه را میپذیرد، آرایه را نگه دارید. اگر به طور مکرر بین لیستها و آرایهها تبدیل میکنید، آن تقاطعها را اندازهگیری کنید. توقف قبل از یک واگذاری پرهزینه، اعداد دقیقی را برای سوال اشتباه ارائه میدهد. ضرب این آرایه یک نتیجه موقت ایجاد میکند. اضافه کردن مقادیر اصلی خروجی را ایجاد میکند. اجرای نیتیو همچنان به حافظه و انتقال داده نیاز دارد.
3:47 برخی عملیات یک بافر خروجی را میپذیرند، که تخصیصها را کاهش میدهد زمانی که کد شما میتواند به طور ایمن از آن استفاده مجدد کند. یک خط زیبا میتواند چندین بافر بزرگ را سفارش دهد. Python Speed این مشکل را نیز برجسته میکند. تخصیصدهنده به زیباییشناسی شما به طرز شگفتانگیزی بیتفاوت است.
آیا یک حلقه پایتون کامپایل شده نیز میتواند سریع باشد؟
4:00 شما همچنین میتوانید نحو حلقه را حفظ کنید و اجرای آن را تغییر دهید. Numba میتواند توابع عددی پایتون پشتیبانی شده را به کد نیتیو کامپایل کند. این حلقه از تست ما با دکوراتور کامپایلر آن است. هنوز میگوید for. مسیر مفسر پرهزینه تغییر کرده است. پس از کامپایل، این حلقه کمتر از نیم میلیثانیه با آرایههای آماده طول کشید. خود کامپایل در این اجرا حدود یک ثانیه طول کشید. این هزینه راهاندازی از زمانبندیهای تکراری حذف شد،
4:22 بنابراین یک کار کوچک یکبار مصرف شایسته مقایسه متفاوتی است. راهنمای خود Numba خوشحال است که حلقههای صریح را نشان دهد. شروع یک مهاجرت زبان قبل از یافتن مسیر داغ یک راه پیچیده برای اجتناب از باز کردن یک پروفایلر است. ابتدا پیدا کنید که چه چیزی زمان را مصرف میکند. انتظارات شبکه، تجزیه شیء و محاسبات انبوه نیاز به رفعهای متفاوتی دارند. عملیات آرایه نیتیو زمانی که محاسبه و داده مناسب باشند کمک میکنند.
4:43 نتیجهای را که فراخواننده شما واقعاً نیاز دارد اندازهگیری کنید. اکنون برای آن میانبر. شما یک تابع پایتون دارید و میخواهید یک آرایه را بپذیرد. یک پوشش به نام vectorize به نظر میرسد دکمه عملکرد گم شده است. مستندات آن یک جمله مهم را در انتظار دارد.
np.vectorize واقعاً چه چیزی را اجرا میکند؟
4:56 NumPy میگوید پوشش vectorize آن عمدتاً برای راحتی ارائه شده است. پیادهسازی آن اساساً یک حلقه for است. این پوشش دستهها را میپذیرد، اما همچنان تابع پایتون شما را برای عناصر متوالی فراخوانی میکند. رابط کوتاهتر شد. مرز اجرا همچنان مشغول بود. ببینید همان مقادیر از طریق فراخوان پایتون برمیگردند. با آرایههای آماده، این نسخه حدود هشتاد و هفت میلیثانیه طول کشید. این کندتر از فهرستفهمی ما بود.
5:18 یک پوشش پخش مفید میتواند دقیقاً کار به ازای هر عنصری را که سعی در حذف آن داشتید حفظ کند. پیادهسازی پشت یک رابط دستهای را بررسی کنید، دادههای سازگار را در شکل مفید آن نگه دارید و تبدیل را در مقایسه لحاظ کنید. حلقههای نیتیو، فراخوان و کامپایل شده میتوانند از دید فراخواننده مشابه به نظر برسند. نام vectorize وعدهای داد که کرونومتر شما هرگز امضا نکرد. آن را نگه دارید زمانی که راحتی چیزی است که نیاز دارید. برای عملکرد، حلقهای که اجرا میشود را بررسی کنید.
چه چیزی را باید در کد خود تغییر دهید؟
5:41 نظر من SHIP IT برای دستهبندی یک مسیر داغ عددی اندازهگیری شده است. حلقههای معمولی را زمانی که به خوبی کار میکنند نگه دارید. بررسی کنید که کار کجا اتفاق میافتد و فراخواننده بعدی به چه چیزی نیاز دارد. عضو شوید، زنگ را بزنید، و در نظرات به من بگویید اگر شما آن را متفاوت مهر میزدید. و این diff امروز است. من نیکو از Axrisi هستم. با مسئولیتپذیری ادغام کنید.
منابع
- Universal functions (ufunc) basics — NumPy 2.2NumPy
- The N-dimensional array — NumPy 2.2NumPy
- numpy.vectorize — convenience and performance notesNumPy
- CPU/SIMD optimizations — NumPy 2.2NumPy
- dis — Disassembler for Python bytecodePython Software Foundation
- CPython 3.10.12 list representationCPython contributors
- The limits of Python vectorization as a performance techniqueItamar Turner-Trauring / Python⇒Speed
- Performance TipsNumba
- Basics of CuPyCuPy



