اکتشاف کامپایل Just-in-Time (JIT) با PyPy. استراتژیهای ادغام عملی را برای افزایش چشمگیر کارایی برنامه پایتون خود بیاموزید. برای توسعهدهندگان جهانی.
باز کردن قفل کارایی پایتون: بررسی عمیق استراتژیهای ادغام PyPy
دهههاست که توسعهدهندگان پایتون را به دلیل نحو ظریف، اکوسیستم گسترده و بهرهوری قابل توجه آن گرامی داشتهاند. با این حال، یک روایت مداوم آن را دنبال میکند: پایتون "کُند" است. در حالی که این یک سادهسازی است، اما درست است که برای کارهای فشرده CPU، مفسر استاندارد CPython میتواند از زبانهای کامپایل شده مانند C++ یا Go عقب بماند. اما اگر بتوانید بدون رها کردن اکوسیستم پایتون که دوست دارید، به کارایی نزدیک به این زبانها دست پیدا کنید، چه؟ وارد PyPy و کامپایلر قدرتمند Just-in-Time (JIT) آن شوید.
این مقاله یک راهنمای جامع برای معماران، مهندسان و سرپرستان فنی نرمافزار جهانی است. ما فراتر از این ادعای ساده که "PyPy سریع است" میرویم و به مکانیک عملی نحوه دستیابی آن به سرعت میپردازیم. مهمتر از آن، ما استراتژیهای عملی و قابل اجرا برای ادغام PyPy در پروژههای شما، شناسایی موارد استفاده ایدهآل و عبور از چالشهای احتمالی را بررسی خواهیم کرد. هدف ما این است که شما را با دانش لازم برای تصمیمگیری آگاهانه در مورد زمان و نحوه استفاده از PyPy برای تقویت برنامههای خود مجهز کنیم.
داستان دو مفسر: CPython در مقابل PyPy
برای درک اینکه چه چیزی PyPy را خاص میکند، ابتدا باید محیط پیشفرضی را که اکثر توسعهدهندگان پایتون در آن کار میکنند، درک کنیم: CPython.
CPython: پیادهسازی مرجع
وقتی پایتون را از python.org دانلود میکنید، CPython را دریافت میکنید. مدل اجرای آن سرراست است:
- تجزیه و کامپایل: فایلهای
.pyقابل خواندن توسط انسان شما تجزیه و به یک زبان میانی مستقل از پلتفرم به نام بایت کد کامپایل میشوند. این همان چیزی است که در فایلهای.pycذخیره میشود. - تفسیر: سپس یک ماشین مجازی (مفسر پایتون) این بایت کد را یک دستور در یک زمان اجرا میکند.
این مدل انعطافپذیری و قابلیت حمل باورنکردنی را فراهم میکند، اما مرحله تفسیر ذاتاً کندتر از اجرای کدی است که مستقیماً به دستورالعملهای ماشین بومی کامپایل شده است. CPython همچنین دارای قفل مفسر سراسری (GIL) معروف است، یک mutex که به تنها یک رشته اجازه میدهد تا بایت کد پایتون را در یک زمان اجرا کند، که به طور موثر موازیسازی چند رشتهای را برای کارهای محدود به CPU محدود میکند.
PyPy: جایگزین مجهز به JIT
PyPy یک مفسر پایتون جایگزین است. جالبترین ویژگی آن این است که تا حد زیادی در یک زیر مجموعه محدود از پایتون به نام RPython (پایتون محدود) نوشته شده است. زنجیره ابزار RPython میتواند این کد را تجزیه و تحلیل کند و یک مفسر سفارشی و بسیار بهینه شده، همراه با یک کامپایلر Just-in-Time تولید کند.
PyPy به جای اینکه فقط بایت کد را تفسیر کند، کار بسیار پیچیدهتری انجام میدهد:
- با تفسیر کد، درست مانند CPython شروع میکند.
- به طور همزمان، کد در حال اجرا را پروفایل میکند و به دنبال حلقهها و توابع پرکاربرد میگردد - اینها اغلب "نقاط داغ" نامیده میشوند.
- هنگامی که یک نقطه داغ شناسایی شد، کامپایلر JIT وارد عمل میشود. بایت کد آن حلقه داغ خاص را به کد ماشین بسیار بهینه شده، متناسب با انواع دادههای خاص مورد استفاده در آن لحظه، ترجمه میکند.
- تماسهای بعدی با این کد، کد ماشین سریع و کامپایل شده را مستقیماً اجرا میکند و به طور کامل از مفسر عبور میکند.
به این شکل به آن فکر کنید: CPython یک مترجم همزمان است که با دقت یک سخنرانی را خط به خط ترجمه میکند، هر بار که به او داده میشود. PyPy مترجمی است که پس از شنیدن یک پاراگراف خاص چندین بار تکرار شده، یک نسخه کامل و از پیش ترجمه شده از آن را مینویسد. دفعه بعد که سخنران آن پاراگراف را میگوید، مترجم PyPy به سادگی ترجمه از پیش نوشته شده و روان را میخواند که چندین مرتبه سریعتر است.
جادوی کامپایل Just-in-Time (JIT)
اصطلاح "JIT" برای ارزش پیشنهادی PyPy اساسی است. بیایید رمز و راز نحوه عملکرد پیادهسازی خاص آن، یک JIT ردیابی را کشف کنیم.
نحوه عملکرد JIT ردیابی PyPy
JIT PyPy سعی نمیکند کل توابع را از قبل کامپایل کند. در عوض، روی با ارزشترین اهداف تمرکز میکند: حلقهها.
- فاز گرم شدن: وقتی برای اولین بار کد خود را اجرا میکنید، PyPy به عنوان یک مفسر استاندارد عمل میکند. بلافاصله سریعتر از CPython نیست. در طول این مرحله اولیه، در حال جمع آوری دادهها است.
- شناسایی حلقههای داغ: پروفایلر شمارندههایی را در هر حلقه در برنامه شما نگه میدارد. هنگامی که شمارنده یک حلقه از آستانه معینی فراتر رفت، به عنوان "داغ" علامتگذاری میشود و ارزش بهینهسازی را دارد.
- ردیابی: JIT شروع به ثبت یک توالی خطی از عملیات اجرا شده در یک تکرار از حلقه داغ میکند. این "ردیابی" است. نه تنها عملیات، بلکه انواع متغیرهای درگیر را نیز ثبت میکند. به عنوان مثال، ممکن است "این دو عدد صحیح را اضافه کنید" را ثبت کند، نه فقط "این دو متغیر را اضافه کنید".
- بهینهسازی و کامپایل: بهینهسازی این ردیابی، که یک مسیر خطی ساده است، بسیار آسانتر از یک تابع پیچیده با شاخههای متعدد است. JIT بهینهسازیهای متعددی (مانند تا کردن ثابت، حذف کد مرده و حرکت کد ثابت در حلقه) را اعمال میکند و سپس ردیابی بهینه شده را به کد ماشین بومی کامپایل میکند.
- نگهبانان و اجرا: کد ماشین کامپایل شده بدون قید و شرط اجرا نمیشود. در ابتدای ردیابی، JIT "نگهبانانی" را وارد میکند. اینها بررسیهای کوچک و سریعی هستند که تأیید میکنند فرضیات انجام شده در طول ردیابی هنوز معتبر هستند. به عنوان مثال، یک نگهبان ممکن است بررسی کند: "آیا متغیر `x` هنوز یک عدد صحیح است؟" اگر همه نگهبانان قبول شوند، کد ماشین فوقالعاده سریع اجرا میشود. اگر یک نگهبان شکست بخورد (به عنوان مثال، `x` اکنون یک رشته است)، اجرا به طور ملایم به مفسر برای آن مورد خاص باز میگردد و ممکن است یک ردیابی جدید برای این مسیر جدید ایجاد شود.
این مکانیسم نگهبان، کلید ماهیت پویای PyPy است. این امکان تخصص و بهینهسازی گسترده را در عین حفظ انعطافپذیری کامل پایتون فراهم میکند.
اهمیت حیاتی گرم شدن
یک نکته مهم این است که مزایای عملکرد PyPy فوری نیست. فاز گرم شدن، جایی که JIT نقاط داغ را شناسایی و کامپایل میکند، زمان و چرخههای CPU را میگیرد. این امر پیامدهای قابل توجهی هم برای معیارگیری و هم برای طراحی برنامه دارد. برای اسکریپتهای بسیار کوتاه، سربار کامپایل JIT گاهی اوقات میتواند PyPy را کندتر از CPython کند. PyPy واقعاً در فرآیندهای طولانی مدت سمت سرور میدرخشد، جایی که هزینه گرم شدن اولیه در هزاران یا میلیونها درخواست مستهلک میشود.
چه زمانی PyPy را انتخاب کنیم: شناسایی موارد استفاده مناسب
PyPy یک ابزار قدرتمند است، نه یک درمان همگانی. استفاده از آن برای مشکل مناسب کلید موفقیت است. افزایش عملکرد میتواند از ناچیز تا بیش از 100 برابر متغیر باشد، که کاملاً به حجم کار بستگی دارد.
نقطه شیرین: محدود به CPU، الگوریتمی، پایتون خالص
PyPy چشمگیرترین افزایش سرعت را برای برنامههایی ارائه میدهد که با مشخصات زیر مطابقت دارند:
- فرآیندهای طولانی مدت: سرورهای وب، پردازندههای کار پسزمینه، خطوط لوله تجزیه و تحلیل دادهها و شبیهسازیهای علمی که برای دقایق، ساعتها یا به طور نامحدود اجرا میشوند. این به JIT زمان کافی برای گرم شدن و بهینهسازی میدهد.
- حجم کار محدود به CPU: گلوگاه برنامه پردازنده است، نه منتظر ماندن برای درخواستهای شبکه یا I/O دیسک. کد زمان خود را در حلقهها، انجام محاسبات و دستکاری ساختارهای داده میگذراند.
- پیچیدگی الگوریتمی: کدی که شامل منطق پیچیده، بازگشت، تجزیه رشته، ایجاد و دستکاری شی و محاسبات عددی است (که قبلاً به یک کتابخانه C منتقل نشدهاند).
- پیادهسازی پایتون خالص: قسمتهای مهم از نظر عملکرد کد در خود پایتون نوشته شدهاند. هرچه کد پایتون بیشتری JIT ببیند و ردیابی کند، بیشتر میتواند بهینهسازی کند.
نمونههایی از برنامههای ایدهآل شامل کتابخانههای سفارشی سریالسازی/غیرسریالسازی دادهها، موتورهای رندر шаблонов, سرورهای بازی، ابزارهای مدلسازی مالی و چارچوبهای ارائه مدلهای یادگیری ماشین خاص (جایی که منطق در پایتون است) هستند.
چه زمانی باید محتاط بود: الگوهای ضد الگو
در برخی سناریوها، PyPy ممکن است سود کمی ارائه دهد یا اصلاً سودی نداشته باشد و حتی ممکن است پیچیدگی را معرفی کند. مراقب این موقعیتها باشید:
- اتکای زیاد به افزونههای CPython C: این مهمترین نکته قابل توجه است. کتابخانههایی مانند NumPy, SciPy و Pandas سنگ بنای اکوسیستم علم داده پایتون هستند. آنها با پیادهسازی منطق اصلی خود در کد C یا Fortran بسیار بهینه شده، که از طریق CPython C API قابل دسترسی است، به سرعت خود دست مییابند. PyPy نمیتواند این کد C خارجی را JIT-کامپایل کند. برای پشتیبانی از این کتابخانهها، PyPy یک لایه شبیهسازی به نام `cpyext` دارد که میتواند کند و شکننده باشد. در حالی که PyPy نسخههای NumPy و Pandas خود (`numpypy`) را دارد، سازگاری و عملکرد میتواند یک چالش بزرگ باشد. اگر گلوگاه برنامه شما از قبل در داخل یک افزونه C باشد، PyPy نمیتواند آن را سریعتر کند و حتی ممکن است به دلیل سربار `cpyext` آن را کند کند.
- اسکریپتهای کوتاه مدت: ابزارهای خط فرمان ساده یا اسکریپتهایی که در چند ثانیه اجرا و خاتمه مییابند احتمالاً سودی نخواهند دید، زیرا زمان گرم شدن JIT بر زمان اجرا غالب خواهد بود.
- برنامههای محدود به I/O: اگر برنامه شما 99% از زمان خود را منتظر بازگشت یک پرس و جو از پایگاه داده یا خواندن یک فایل از یک اشتراک شبکه میگذراند، سرعت مفسر پایتون نامربوط است. بهینهسازی مفسر از 1x به 10x تأثیر ناچیزی بر عملکرد کلی برنامه خواهد داشت.
استراتژیهای ادغام عملی
شما یک مورد استفاده بالقوه را شناسایی کردهاید. چگونه PyPy را واقعاً ادغام میکنید؟ در اینجا سه استراتژی اصلی وجود دارد، از ساده تا معماری پیچیده.
استراتژی 1: رویکرد "جایگزینی قطرهای"
این سادهترین و مستقیمترین روش است. هدف این است که کل برنامه موجود خود را با استفاده از مفسر PyPy به جای مفسر CPython اجرا کنید.
فرآیند:
- نصب: نسخه مناسب PyPy را نصب کنید. استفاده از ابزاری مانند `pyenv` برای مدیریت چندین مفسر پایتون در کنار هم بسیار توصیه میشود. به عنوان مثال: `pyenv install pypy3.9-7.3.9`.
- محیط مجازی: یک محیط مجازی اختصاصی برای پروژه خود با استفاده از PyPy ایجاد کنید. این وابستگیهای آن را جدا میکند. مثال: `pypy3 -m venv pypy_env`.
- فعالسازی و نصب: محیط را فعال کنید (`source pypy_env/bin/activate`) و وابستگیهای پروژه خود را با استفاده از `pip` نصب کنید: `pip install -r requirements.txt`.
- اجرا و معیارگیری: نقطه ورودی برنامه خود را با استفاده از مفسر PyPy در محیط مجازی اجرا کنید. به طور حیاتی، معیارگیری دقیق و واقعبینانه را برای اندازهگیری تأثیر انجام دهید.
چالشها و ملاحظات:
- سازگاری وابستگی: این مرحله تعیین کننده است. کتابخانههای پایتون خالص تقریباً همیشه بدون نقص کار میکنند. با این حال، هر کتابخانهای با یک جزء افزونه C ممکن است در نصب یا اجرا با شکست مواجه شود. شما باید سازگاری هر وابستگی را به دقت بررسی کنید. گاهی اوقات، نسخه جدیدتری از یک کتابخانه از PyPy پشتیبانی کرده است، بنابراین بهروزرسانی وابستگیهای شما اولین گام خوب است.
- مشکل افزونه C: اگر یک کتابخانه مهم ناسازگار باشد، این استراتژی با شکست مواجه میشود. شما باید یا یک کتابخانه جایگزین پایتون خالص پیدا کنید، در پروژه اصلی برای افزودن پشتیبانی PyPy مشارکت کنید یا یک استراتژی ادغام متفاوت را اتخاذ کنید.
استراتژی 2: سیستم ترکیبی یا Polyglot
این یک رویکرد قدرتمند و عملگرایانه برای سیستمهای بزرگ و پیچیده است. به جای انتقال کل برنامه به PyPy، شما فقط PyPy را به اجزای خاص و حیاتی از نظر عملکرد اعمال میکنید، جایی که بیشترین تأثیر را خواهد داشت.
الگوهای پیادهسازی:
- معماری میکروسرویس: منطق محدود به CPU را در میکروسرویس خود جدا کنید. این سرویس میتواند به عنوان یک برنامه PyPy مستقل ساخته و مستقر شود. بقیه سیستم شما، که ممکن است روی CPython اجرا شود (به عنوان مثال، یک فرانتاند وب Django یا Flask)، از طریق یک API به خوبی تعریف شده (مانند REST, gRPC یا یک صف پیام) با این سرویس با کارایی بالا ارتباط برقرار میکند. این الگو جداسازی عالی را فراهم میکند و به شما امکان میدهد از بهترین ابزار برای هر کار استفاده کنید.
- کارگران مبتنی بر صف: این یک الگوی کلاسیک و بسیار موثر است. یک برنامه CPython ("تولید کننده") کارهای محاسباتی سنگین را در یک صف پیام قرار میدهد (مانند RabbitMQ, Redis یا SQS). یک استخر جداگانه از فرآیندهای کارگر، که روی PyPy اجرا میشود ("مصرف کنندگان") این کارها را برمیدارد، کارهای سنگین را با سرعت بالا اجرا میکند و نتایج را در جایی ذخیره میکند که برنامه اصلی بتواند به آنها دسترسی داشته باشد. این برای کارهایی مانند کدگذاری ویدئو، تولید گزارش یا تجزیه و تحلیل دادههای پیچیده عالی است.
رویکرد ترکیبی اغلب واقعبینانهترین رویکرد برای پروژههای تثبیت شده است، زیرا خطر را به حداقل میرساند و امکان پذیرش تدریجی PyPy را بدون نیاز به بازنویسی کامل یا مهاجرت وابستگی دردناک برای کل کدپایه فراهم میکند.
استراتژی 3: مدل توسعه CFFI-First
این یک استراتژی فعال برای پروژههایی است که میدانند هم به عملکرد بالا و هم به تعامل با کتابخانههای C نیاز دارند (به عنوان مثال، برای بستهبندی یک سیستم قدیمی یا یک SDK با کارایی بالا).
به جای استفاده از CPython C API سنتی، شما از کتابخانه C Foreign Function Interface (CFFI) استفاده میکنید. CFFI از ابتدا طوری طراحی شده است که مستقل از مفسر باشد و به طور یکپارچه در هر دو CPython و PyPy کار میکند.
چرا با PyPy بسیار موثر است:
JIT PyPy در مورد CFFI فوقالعاده باهوش است. هنگام ردیابی حلقهای که یک تابع C را از طریق CFFI فراخوانی میکند، JIT اغلب میتواند "از طریق" لایه CFFI "ببیند". آن تماس تابع را درک میکند و میتواند کد ماشین تابع C را مستقیماً در ردیابی کامپایل شده تعبیه کند. نتیجه این است که سربار فراخوانی تابع C از پایتون عملاً در یک حلقه داغ ناپدید میشود. انجام این کار با CPython C API پیچیده برای JIT بسیار سختتر است.
توصیه قابل اجرا: اگر در حال شروع یک پروژه جدید هستید که نیاز به رابط با کتابخانههای C/C++/Rust/Go دارد و پیشبینی میکنید که عملکرد نگران کننده باشد، استفاده از CFFI از روز اول یک انتخاب استراتژیک است. این گزینههای شما را باز نگه میدارد و انتقال آینده به PyPy را برای افزایش عملکرد به یک تمرین ساده تبدیل میکند.
معیارگیری و اعتبارسنجی: اثبات دستاوردها
هرگز فرض نکنید PyPy سریعتر خواهد بود. همیشه اندازه گیری کنید. معیارگیری مناسب در هنگام ارزیابی PyPy غیرقابل مذاکره است.
محاسبه گرم شدن
یک معیارگیری سادهلوحانه میتواند گمراه کننده باشد. به سادگی زمانبندی یک اجرای واحد از یک تابع با استفاده از `time.time()` شامل گرم شدن JIT میشود و عملکرد حالت پایدار واقعی را منعکس نمیکند. یک معیارگیری صحیح باید:
- کدی را که باید اندازهگیری شود، چندین بار در یک حلقه اجرا کنید.
- چند تکرار اول را دور بریزید یا قبل از شروع تایمر، یک فاز گرم شدن اختصاصی را اجرا کنید.
- زمان اجرای متوسط را در تعداد زیادی اجرا پس از اینکه JIT فرصتی برای کامپایل همه چیز داشته است، اندازه گیری کنید.
ابزارها و تکنیکها
- معیارهای خرد: برای توابع کوچک و مجزا، ماژول داخلی پایتون `timeit` نقطه شروع خوبی است زیرا حلقهها و زمانبندی را به درستی مدیریت میکند.
- معیارگیری ساختاریافته: برای آزمایشهای رسمیتر که در مجموعه آزمایشی شما ادغام شدهاند، کتابخانههایی مانند `pytest-benchmark` فیکچرهای قدرتمندی را برای اجرای و تجزیه و تحلیل معیارها، از جمله مقایسهها بین اجراها ارائه میدهند.
- معیارگیری سطح برنامه: برای سرویسهای وب، مهمترین معیار عملکرد سرتاسر تحت بار واقعی است. از ابزارهای تست بار مانند `locust`, `k6` یا `JMeter` برای شبیهسازی ترافیک دنیای واقعی در برابر برنامه خود که روی CPython و PyPy اجرا میشود استفاده کنید و متریکهایی مانند درخواست در ثانیه، تأخیر و نرخ خطا را مقایسه کنید.
- پروفایل حافظه: عملکرد فقط مربوط به سرعت نیست. از ابزارهای پروفایل حافظه (`tracemalloc`, `memory-profiler`) برای مقایسه مصرف حافظه استفاده کنید. PyPy اغلب دارای یک پروفایل حافظه متفاوت است. جمعآوریکننده زباله پیشرفتهتر آن گاهی اوقات میتواند منجر به مصرف حافظه اوج پایینتری برای برنامههای طولانیمدت با اشیاء فراوان شود، اما اثر حافظه خط پایه آن ممکن است کمی بالاتر باشد.
اکوسیستم PyPy و راه پیش رو
داستان سازگاری در حال تکامل
تیم PyPy و جامعه گستردهتر گامهای عظیمی در سازگاری برداشتهاند. بسیاری از کتابخانههای محبوبی که زمانی مشکلساز بودند، اکنون از پشتیبانی عالی PyPy برخوردار هستند. همیشه برای آخرین اطلاعات سازگاری، وب سایت رسمی PyPy و مستندات کتابخانههای کلیدی خود را بررسی کنید. وضعیت دائماً در حال بهبود است.
نگاهی اجمالی به آینده: HPy
مشکل افزونه C بزرگترین مانع برای پذیرش جهانی PyPy باقی مانده است. جامعه فعالانه در حال کار بر روی یک راه حل بلند مدت است: HPy (HpyProject.org). HPy یک C API جدید و طراحی شده مجدد برای پایتون است. برخلاف CPython C API، که جزئیات داخلی مفسر CPython را نشان میدهد، HPy یک رابط انتزاعی و جهانیتر را ارائه میدهد.
وعده HPy این است که نویسندگان ماژول افزونه میتوانند کد خود را یک بار در برابر HPy API بنویسند و به طور موثر بر روی چندین مفسر، از جمله CPython, PyPy و سایرین کامپایل و اجرا شود. هنگامی که HPy به طور گسترده پذیرفته شود، تمایز بین کتابخانههای "پایتون خالص" و "افزونه C" کمتر نگران کننده عملکرد خواهد بود و به طور بالقوه انتخاب مفسر را به یک سوئیچ پیکربندی ساده تبدیل میکند.
نتیجهگیری: یک ابزار استراتژیک برای توسعهدهنده مدرن
PyPy یک جایگزین جادویی برای CPython نیست که بتوانید آن را کورکورانه اعمال کنید. این یک قطعه مهندسی بسیار تخصصی و فوقالعاده قدرتمند است که در صورت استفاده از آن برای مشکل مناسب، میتواند بهبودهای عملکردی شگفتانگیزی را به همراه داشته باشد. این پایتون را از یک "زبان اسکریپتنویسی" به یک پلتفرم با کارایی بالا تبدیل میکند که قادر به رقابت با زبانهای کامپایل شده ایستا برای طیف گستردهای از وظایف محدود به CPU است.
برای استفاده موفقیتآمیز از PyPy، این اصول کلیدی را به خاطر بسپارید:
- حجم کار خود را درک کنید: آیا محدود به CPU است یا محدود به I/O؟ آیا طولانی مدت است؟ آیا گلوگاه در کد پایتون خالص است یا یک افزونه C؟
- استراتژی مناسب را انتخاب کنید: اگر وابستگیها اجازه میدهند، با جایگزینی قطرهای ساده شروع کنید. برای سیستمهای پیچیده، از یک معماری ترکیبی با استفاده از میکروسرویسها یا صفهای کارگر استفاده کنید. برای پروژههای جدید، یک رویکرد CFFI-first را در نظر بگیرید.
- به طور مذهبی معیارگیری کنید: اندازهگیری کنید، حدس نزنید. برای دریافت دادههای عملکرد دقیق که اجرای حالت پایدار و واقعی را منعکس میکنند، گرم شدن JIT را محاسبه کنید.
دفعه بعد که با یک گلوگاه عملکرد در یک برنامه پایتون مواجه شدید، بلافاصله به سراغ یک زبان متفاوت نروید. نگاهی جدی به PyPy بیندازید. با درک نقاط قوت آن و اتخاذ یک رویکرد استراتژیک برای ادغام، میتوانید سطح جدیدی از عملکرد را باز کنید و به ساختن چیزهای شگفتانگیز با زبانی که میدانید و دوست دارید ادامه دهید.