diff --git a/.translate/state/numba.md.yml b/.translate/state/numba.md.yml index d5a9787..73a8e2c 100644 --- a/.translate/state/numba.md.yml +++ b/.translate/state/numba.md.yml @@ -1,6 +1,6 @@ -source-sha: 21f1ea0669031ccc0ee0194878439a87de5d248d -synced-at: "2026-04-10" +source-sha: a897eb875ecbf8975872fd6bc0a6b2e91a6ba701 +synced-at: "2026-04-12" model: claude-sonnet-4-6 mode: UPDATE -section-count: 7 +section-count: 6 tool-version: 0.14.1 diff --git a/lectures/numba.md b/lectures/numba.md index 6b6e39c..b5b92e8 100644 --- a/lectures/numba.md +++ b/lectures/numba.md @@ -13,10 +13,9 @@ translation: title: Numba headings: Overview: مروری کلی - Compiling Functions: کامپایل کردن توابع + Compiling Functions: کامپایل توابع Compiling Functions::An Example: یک مثال Compiling Functions::How and When it Works: چگونه و چه زمانی کار می‌کند - Decorator Notation: نماد Decorator Type Inference: استنتاج نوع Dangers and Limitations: خطرات و محدودیت‌ها Dangers and Limitations::Limitations: محدودیت‌ها @@ -25,7 +24,7 @@ translation: Exercises: تمرین‌ها --- -(speed)= +(numba_lecture)= ```{raw} jupyter
@@ -59,34 +58,33 @@ import matplotlib.pyplot as plt در یک {doc}`درس قبلی ` درباره برداری‌سازی بحث کردیم، که می‌تواند سرعت اجرا را با ارسال دسته‌ای عملیات پردازش آرایه به کد کارآمد سطح پایین بهبود بخشد. -با این حال، همانطور که {ref}`در آن درس بحث شد `، -طرح‌های سنتی برداری‌سازی، مانند آنچه در MATLAB، Julia و NumPy یافت می‌شود، نقاط ضعفی دارند. +با این حال، همانطور که {ref}`قبلاً بحث شد `، طرح‌های سنتی برداری‌سازی، مانند آنچه در MATLAB و NumPy یافت می‌شود، چندین نقطه ضعف دارند. -* برای عملیات ترکیبی آرایه‌ها بسیار حافظه‌بر هستند -* برای برخی الگوریتم‌ها ناکارآمد یا غیرممکن است. +* برای عملیات ترکیبی آرایه، بسیار حافظه‌بر هستند +* برای برخی الگوریتم‌ها، ناکارآمد یا غیرممکن است. یک راه برای دور زدن این مشکلات، استفاده از [Numba](https://numba.pydata.org/) است، یک **کامپایلر در زمان اجرا (JIT)** برای Python که به سمت کار عددی جهت‌گیری شده است. Numba توابع را در حین اجرا به دستورالعمل‌های کد ماشین بومی کامپایل می‌کند. -وقتی موفق می‌شود، Numba با کد ماشین زبان‌های سطح پایین برابری خواهد کرد. +وقتی موفق می‌شود، نتیجه عملکردی قابل مقایسه با C یا Fortran کامپایل‌شده است. علاوه بر این، Numba می‌تواند ترفندهای مفید دیگری نیز انجام دهد، مانند {ref}`چندنخی ` یا ارتباط با GPU‌ها (از طریق `numba.cuda`). -کامپایلر JIT نامبا از بسیاری جهات مشابه کامپایلر JIT در JULIA است. +کامپایلر JIT در Numba از بسیاری جهات مشابه کامپایلر JIT در Julia است. -تفاوت اصلی در این است که کمتر جاه‌طلبانه است و تلاش می‌کند زیرمجموعه کوچک‌تری از زبان را کامپایل کند. +تفاوت اصلی این است که کمتر بلندپروازانه است و تلاش می‌کند زیرمجموعه کوچک‌تری از زبان را کامپایل کند. -اگرچه این ممکن است مانند یک نقص به نظر برسد، اما از برخی جهات یک مزیت محسوب می‌شود. +اگرچه این ممکن است به نظر یک نقص برسد، اما به نوعی یک مزیت است. -Numba سبک، آسان برای استفاده و بسیار خوب در آنچه انجام می‌دهد است. +Numba سبک، آسان برای استفاده، و در آنچه انجام می‌دهد بسیار خوب است. این درس ایده‌های اصلی را معرفی می‌کند. (numba_link)= -## {index}`کامپایل کردن توابع ` +## {index}`کامپایل توابع ` ```{index} single: Python; Numba ``` @@ -103,13 +101,13 @@ $$ x_{t+1} = \alpha x_t (1 - x_t) $$ -در ادامه مقدار زیر را تنظیم می‌کنیم: +در ادامه تنظیم می‌کنیم: ```{code-cell} ipython3 α = 4.0 ``` -در اینجا نمودار یک مسیر معمول را مشاهده می‌کنید که از $x_0 = 0.1$ شروع می‌شود و $t$ روی محور x قرار دارد: +در اینجا نمودار یک مسیر نمونه‌وار، با شروع از $x_0 = 0.1$، با $t$ بر روی محور x آمده است: ```{code-cell} ipython3 def qm(x0, n): @@ -127,7 +125,7 @@ ax.set_ylabel('$x_{t}$', fontsize = 12) plt.show() ``` -برای تسریع تابع `qm` با استفاده از Numba، اولین گام ما این است: +برای افزایش سرعت تابع `qm` با استفاده از Numba، اولین گام ما این است: ```{code-cell} ipython3 from numba import jit @@ -137,9 +135,9 @@ qm_numba = jit(qm) تابع `qm_numba` نسخه‌ای از `qm` است که برای کامپایل JIT «هدف‌گذاری» شده است. -به زودی توضیح خواهیم داد که این به چه معناست. +لحظه‌ای بعد توضیح خواهیم داد که این به چه معناست. -بیایید فراخوانی‌های یکسان تابع را در این دو نسخه زمان‌بندی و مقایسه کنیم، ابتدا با تابع اصلی `qm`: +بیایید فراخوانی‌های تابع یکسان را در این دو نسخه زمان‌بندی و مقایسه کنیم، با شروع از تابع اصلی `qm`: ```{code-cell} ipython3 n = 10_000_000 @@ -149,7 +147,7 @@ with qe.Timer() as timer1: time1 = timer1.elapsed ``` -حالا بیایید qm_numba را امتحان کنیم: +حالا بیایید `qm_numba` را امتحان کنیم: ```{code-cell} ipython3 with qe.Timer() as timer2: @@ -159,7 +157,7 @@ time2 = timer2.elapsed این از قبل یک افزایش سرعت بسیار زیاد است. -در واقع، دفعه بعد و تمام دفعات بعدی حتی سریع‌تر اجرا می‌شود زیرا تابع کامپایل شده و در حافظه قرار دارد: +در واقع، دفعه بعد و تمام دفعات بعدی حتی سریع‌تر اجرا می‌شود، چرا که تابع کامپایل شده و در حافظه قرار دارد: (qm_numba_result)= @@ -175,71 +173,34 @@ time1 / time3 # Calculate speed gain ### چگونه و چه زمانی کار می‌کند -Numba تلاش می‌کند با استفاده از زیرساختی که [پروژه LLVM](https://llvm.org/) فراهم کرده، کد ماشین سریع تولید کند. +Numba تلاش می‌کند با استفاده از زیرساخت ارائه‌شده توسط [پروژه LLVM](https://llvm.org/) کد ماشین سریع تولید کند. -این کار را با استنتاج اطلاعات نوع به صورت لحظه‌ای انجام می‌دهد. +این کار را از طریق استنتاج اطلاعات نوع به صورت بلادرنگ انجام می‌دهد. -(برای بحث درباره انواع، {doc}`درس قبلی ` ما درباره محاسبات علمی را ببینید.) +(برای بحث درباره نوع‌ها، {doc}`درس ` قبلی ما در مورد محاسبات علمی را ببینید.) -ایده اصلی این است: +ایده اساسی این است: -* پایتون بسیار انعطاف‌پذیر است و از این رو می‌توانیم تابع qm را با انواع مختلفی فراخوانی کنیم. - * مثلاً `x0` می‌تواند یک آرایه NumPy یا یک لیست باشد، `n` می‌تواند یک عدد صحیح یا یک عدد اعشاری باشد و غیره. -* این امر تولید کد ماشین کارآمد *پیش از زمان اجرا* را بسیار دشوار می‌سازد. -* اما وقتی واقعاً تابع را *فراخوانی* می‌کنیم، مثلاً با اجرای `qm(0.5, 10)`، انواع `x0` و `n` مشخص می‌شوند. -* علاوه بر این، انواع *سایر متغیرها* در `qm` *می‌توانند پس از مشخص شدن انواع ورودی استنتاج شوند*. -* بنابراین استراتژی Numba و سایر کامپایلرهای JIT این است که *تا زمان فراخوانی تابع صبر کنند* و سپس کامپایل کنند. +* پایتون بسیار انعطاف‌پذیر است و از این رو می‌توانیم تابع `qm` را با انواع مختلفی فراخوانی کنیم. + * به عنوان مثال، `x0` می‌تواند یک آرایه NumPy یا یک لیست باشد، `n` می‌تواند یک عدد صحیح یا یک عدد اعشاری باشد و غیره. +* این امر تولید کد ماشین کارآمد *از پیش* (یعنی قبل از زمان اجرا) را بسیار دشوار می‌کند. +* اما وقتی در واقع تابع را *فراخوانی* می‌کنیم، مثلاً با اجرای `qm(0.5, 10)`، + نوع‌های `x0` و `n` مشخص می‌شوند. +* علاوه بر این، نوع‌های *سایر متغیرها* در `qm` *پس از مشخص شدن نوع‌های ورودی قابل استنتاج هستند*. +* بنابراین استراتژی Numba و سایر کامپایلرهای JIT این است که *تا زمان فراخوانی تابع صبر کنند*، و سپس کامپایل کنند. -به همین دلیل است که به آن کامپایل «درست به موقع» (just-in-time) گفته می‌شود. +این روش «کامپایل در لحظه» نام دارد. -توجه داشته باشید که اگر `qm(0.5, 10)` را فراخوانی کنید و سپس `qm(0.9, 20)` را دنبال آن بیاورید، کامپایل تنها در اولین فراخوانی انجام می‌شود. +توجه داشته باشید که اگر `qm(0.5, 10)` را فراخوانی کنید و سپس `qm(0.9, 20)` را دنبال آن بیاورید، کامپایل فقط در فراخوانی اول انجام می‌شود. -این به این دلیل است که کد کامپایل‌شده در حافظه نهان ذخیره می‌شود و در صورت نیاز مجدداً استفاده می‌شود. +این به این دلیل است که کد کامپایل‌شده در حافظه نهان ذخیره و در صورت نیاز مجدداً استفاده می‌شود. -به همین دلیل است که در کد بالا، `time3` کوچک‌تر از `time2` است. +به همین دلیل است که در کد بالا، `time3` از `time2` کوچک‌تر است. -## نماد Decorator - -در کد بالا، نسخه کامپایل شده JIT از `qm` را از طریق فراخوانی ایجاد کردیم - -```{code-cell} ipython3 -qm_numba = jit(qm) -``` - -در عمل، این معمولاً با استفاده از یک سینتکس جایگزین *decorator* انجام می‌شود. - -(ما decoratorها را در یک {doc}`درس جداگانه ` بحث می‌کنیم اما می‌توانید جزئیات را در این مرحله رد کنید.) - -به طور مشخص، برای هدف‌گیری یک تابع برای کامپایل JIT، می‌توانیم `@jit` را قبل از تعریف تابع قرار دهیم. - -در اینجا نحوه انجام این کار برای `qm` آمده است - -```{code-cell} ipython3 -@jit -def qm(x0, n): - x = np.empty(n+1) - x[0] = x0 - for t in range(n): - x[t+1] = α * x[t] * (1 - x[t]) - return x +```{admonition} توضیح +در عمل، به جای نوشتن `qm_numba = jit(qm)`، از نحو *دکوراتور* استفاده می‌کنیم و `@jit` را قبل از تعریف تابع قرار می‌دهیم. این معادل اضافه کردن `qm = jit(qm)` بعد از تعریف است. ما از این نحو در بقیه درس استفاده می‌کنیم. (برای اطلاعات بیشتر در مورد دکوراتورها، {doc}`python_advanced_features` را ببینید.) ``` -این معادل افزودن `qm = jit(qm)` بعد از تعریف تابع است. - -موارد زیر اکنون از نسخه jitted استفاده می‌کنند: - -```{code-cell} ipython3 -with qe.Timer(precision=4): - qm(0.1, 100_000) -``` - -```{code-cell} ipython3 -with qe.Timer(precision=4): - qm(0.1, 100_000) -``` - -Numba همچنین چندین آرگومان برای decoratorها جهت تسریع محاسبات و ذخیره‌سازی cache توابع ارائه می‌دهد -- [اینجا](https://numba.readthedocs.io/en/stable/user/performance-tips.html) را ببینید. - ## استنتاج نوع استنتاج موفقیت‌آمیز نوع، بخش کلیدی کامپایل JIT است. @@ -250,45 +211,39 @@ Numba همچنین با آرایه‌های NumPy که انواع به خوبی در یک محیط ایده‌آل، Numba می‌تواند تمام اطلاعات نوع لازم را استنتاج کند. -این به آن اجازه می‌دهد تا کد ماشین بومی کارآمدی تولید کند، بدون نیاز به فراخوانی محیط زمان اجرای Python. +این به آن اجازه می‌دهد تا کد ماشین بومی تولید کند، بدون نیاز به فراخوانی محیط زمان اجرای Python. وقتی Numba نمی‌تواند تمام اطلاعات نوع را استنتاج کند، خطا ایجاد می‌کند. -به عنوان مثال، در تنظیم (مصنوعی) زیر، Numba قادر به تعیین نوع تابع `mean` هنگام کامپایل تابع `bootstrap` نیست +به عنوان مثال، در تنظیم زیر، Numba قادر به تعیین نوع تابع `g` هنگام کامپایل `iterate` نیست ```{code-cell} ipython3 @jit -def bootstrap(data, statistics, n_resamples): - bootstrap_stat = np.empty(n_resamples) - n = len(data) - for i in range(n_resamples): - resample = np.random.choice(data, size=n, replace=True) - bootstrap_stat[i] = statistics(resample) - return bootstrap_stat - -# هیچ decorator اینجا نیست. -def mean(data): - return np.mean(data) +def iterate(f, x0, n): + x = x0 + for t in range(n): + x = f(x) + return x -data = np.array((2.3, 3.1, 4.3, 5.9, 2.1, 3.8, 2.2)) -n_resamples = 10 +# Not jitted +def g(x): + return np.cos(x) - 2 * np.sin(x) # این کد خطا ایجاد می‌کند try: - bootstrap(data, mean, n_resamples) + iterate(g, 0.5, 100) except Exception as e: print(e) ``` -می‌توانیم این خطا را در این مورد به راحتی با کامپایل کردن `mean` برطرف کنیم. +می‌توانیم این خطا را به راحتی با کامپایل کردن `g` برطرف کنیم. ```{code-cell} ipython3 @jit -def mean(data): - return np.mean(data) +def g(x): + return np.cos(x) - 2 * np.sin(x) -with qe.Timer(): - bootstrap(data, mean, n_resamples) +iterate(g, 0.5, 100) ``` ## خطرات و محدودیت‌ها @@ -299,7 +254,7 @@ with qe.Timer(): همانطور که دیدیم، Numba باید اطلاعات نوع را روی تمام متغیرها استنتاج کند تا دستورالعمل‌های سریع سطح ماشین تولید کند. -برای روال‌های بزرگ یا روال‌هایی که از کتابخانه‌های خارجی استفاده می‌کنند، این فرایند به راحتی می‌تواند شکست بخورد. +برای روال‌های بزرگ‌تر، یا برای روال‌هایی که از کتابخانه‌های خارجی استفاده می‌کنند، این فرایند به راحتی می‌تواند شکست بخورد. از این رو، بهتر است روی تسریع قطعات کوچک و حیاتی کد تمرکز کنید. @@ -331,12 +286,14 @@ print(add_a(10)) وقتی Numba کد ماشین را برای توابع کامپایل می‌کند، متغیرهای سراسری را به عنوان ثابت برای اطمینان از پایداری نوع درمان می‌کند. +برای جلوگیری از این مشکل، مقادیر را به عنوان آرگومان تابع ارسال کنید و به متغیرهای سراسری متکی نباشید. + (multithreading)= ## حلقه‌های چندنخی در Numba -علاوه بر کامپایل JIT، Numba پشتیبانی از محاسبات موازی در CPUها و GPUها ارائه می‌دهد. +علاوه بر کامپایل JIT، Numba پشتیبانی از محاسبات موازی در CPUها ارائه می‌دهد. -ابزار کلیدی برای موازی‌سازی در CPUها در Numba تابع `prange` است که به Numba می‌گوید تا تکرارهای حلقه را به صورت موازی در هسته‌های موجود اجرا کند. +ابزار کلیدی برای موازی‌سازی در Numba تابع `prange` است که به Numba می‌گوید تا تکرارهای حلقه را به صورت موازی در هسته‌های CPU موجود اجرا کند. برای نمایش، ابتدا به یک قطعه کد ساده تک‌نخی (یعنی غیرموازی) نگاه می‌کنیم. @@ -357,8 +314,6 @@ $$ در اینجا کد است: ```{code-cell} ipython3 -from numba import jit - @jit def h(w, r=0.1, s=0.3, v1=0.1, v2=1.0): """ @@ -395,8 +350,8 @@ plt.show() حل این موضوع با مداد و کاغذ آسان نیست، بنابراین به جای آن از شبیه‌سازی استفاده خواهیم کرد: -1. تعداد زیادی از خانوارها را در طول زمان شبیه‌سازی می‌کنیم -2. میانه ثروت را محاسبه می‌کنیم +1. تعداد زیادی از خانوارها را در طول زمان شبیه‌سازی کنیم +2. میانه ثروت را محاسبه کنیم در اینجا کد است: @@ -450,7 +405,9 @@ with qe.Timer(): افزایش سرعت قابل توجه است. -توجه داشته باشید که موازی‌سازی را در سطح خانوارها انجام می‌دهیم نه در طول زمان -- به‌روزرسانی‌های یک خانوار در دوره‌های زمانی مختلف ذاتاً ترتیبی هستند. +توجه کنید که موازی‌سازی را در سطح خانوارها انجام می‌دهیم نه در طول زمان — به‌روزرسانی‌های یک خانوار در طول دوره‌های زمانی ذاتاً ترتیبی هستند. + +برای موازی‌سازی مبتنی بر GPU، به {doc}`درس‌های ما درباره JAX ` مراجعه کنید. ## تمرین‌ها @@ -507,8 +464,7 @@ with qe.Timer(): :label: speed_ex2 ``` -در سری درس‌های [مقدمه‌ای بر اقتصاد کمی با -Python](https://intro.quantecon.org/intro.html) می‌توانید همه چیز درباره زنجیره‌های مارکوف حالت محدود یاد بگیرید. +در سری سخنرانی [مقدمه‌ای بر اقتصاد کمی با Python](https://intro.quantecon.org/intro.html) می‌توانید همه چیز درباره زنجیره‌های مارکوف حالت محدود یاد بگیرید. فعلاً، فقط روی شبیه‌سازی یک مثال بسیار ساده از چنین زنجیره‌ای تمرکز کنیم. @@ -695,7 +651,7 @@ $$ که در آن -1. $\beta$ یک ضریب تنزیل است، +1. $\beta$ یک فاکتور تنزیل است، 2. $n$ تاریخ انقضا است، 2. $K$ قیمت اعمال است و 3. $\{S_t\}$ قیمت دارایی پایه در هر زمان $t$ است. @@ -716,7 +672,7 @@ $$ h_{t+1} = \rho h_t + \nu \eta_{t+1} $$ -در اینجا $\{\xi_t\}$ و $\{\eta_t\}$ مستقل و هم‌توزیع و نرمال استاندارد هستند. +در اینجا $\{\xi_t\}$ و $\{\eta_t\}$ IID و نرمال استاندارد هستند. (این یک مدل **نوسان تصادفی** است، که در آن نوسان $\sigma_t$ در طول زمان تغییر می‌کند.) @@ -724,7 +680,7 @@ $$ (در اینجا `S0` همان $S_0$ و `h0` همان $h_0$ است.) -با تولید $M$ مسیر $s_0, \ldots, s_n$، تخمین مونت‌کارلو را محاسبه کنید +با تولید $M$ مسیر $s_0, \ldots, s_n$، تخمین Monte Carlo را محاسبه کنید $$ \hat P_M