You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
برای تسریع تابع `qm` با استفاده از Numba، اولین گام ما این است:
122
-
123
-
```{code-cell} ipython3
124
-
from numba import jit
125
-
126
-
qm_numba = jit(qm)
127
-
```
128
-
129
-
تابع `qm_numba` نسخهای از `qm` است که برای کامپایل JIT «هدفگذاری» شده است.
130
-
131
-
به زودی توضیح خواهیم داد که این به چه معناست.
132
-
133
-
بیایید فراخوانیهای یکسان تابع را در این دو نسخه زمانبندی و مقایسه کنیم، ابتدا با تابع اصلی `qm`:
115
+
بیایید ببینیم برای $n$ بزرگ، اجرای این تابع چقدر طول میکشد:
134
116
135
117
```{code-cell} ipython3
136
118
n = 10_000_000
137
119
138
120
with qe.Timer() as timer1:
139
-
qm(0.1, int(n))
140
-
time1 = timer1.elapsed
141
-
```
142
-
143
-
حالا بیایید qm_numba را امتحان کنیم:
121
+
# Time Python base version
122
+
x = qm(0.1, n)
144
123
145
-
```{code-cell} ipython3
146
-
with qe.Timer() as timer2:
147
-
qm_numba(0.1, int(n))
148
-
time2 = timer2.elapsed
149
124
```
150
125
151
-
این از قبل یک افزایش سرعت بسیار زیاد است.
152
-
153
-
در واقع، دفعه بعد و تمام دفعات بعدی حتی سریعتر اجرا میشود زیرا تابع کامپایل شده و در حافظه قرار دارد:
126
+
#### شتابدهی از طریق Numba
154
127
155
-
(qm_numba_result)=
128
+
برای افزایش سرعت تابع `qm` با استفاده از Numba، ابتدا تابع `jit` را وارد میکنیم:
156
129
157
130
```{code-cell} ipython3
158
-
with qe.Timer() as timer3:
159
-
qm_numba(0.1, int(n))
160
-
time3 = timer3.elapsed
161
-
```
162
-
163
-
```{code-cell} ipython3
164
-
time1 / time3 # Calculate speed gain
131
+
from numba import jit
165
132
```
166
133
167
-
### چگونه و چه زمانی کار میکند
168
-
169
-
Numba تلاش میکند با استفاده از زیرساختی که [پروژه LLVM](https://llvm.org/) فراهم کرده، کد ماشین سریع تولید کند.
170
-
171
-
این کار را با استنتاج اطلاعات نوع به صورت لحظهای انجام میدهد.
172
-
173
-
(برای بحث درباره انواع، {doc}`درس قبلی <need_for_speed>` ما درباره محاسبات علمی را ببینید.)
174
-
175
-
ایده اصلی این است:
176
-
177
-
* پایتون بسیار انعطافپذیر است و از این رو میتوانیم تابع qm را با انواع مختلفی فراخوانی کنیم.
178
-
* مثلاً `x0` میتواند یک آرایه NumPy یا یک لیست باشد، `n` میتواند یک عدد صحیح یا یک عدد اعشاری باشد و غیره.
179
-
* این امر *پیش*-کامپایل کردن تابع (یعنی کامپایل پیش از زمان اجرا) را دشوار میسازد.
180
-
* اما وقتی واقعاً تابع را فراخوانی میکنیم، مثلاً با اجرای `qm(0.5, 10)`، انواع `x0` و `n` مشخص میشوند.
181
-
* علاوه بر این، انواع *سایر متغیرها* در `qm`*میتوانند پس از مشخص شدن انواع ورودی استنتاج شوند*.
182
-
* بنابراین استراتژی Numba و سایر کامپایلرهای JIT این است که تا این لحظه صبر کنند و سپس تابع را کامپایل کنند.
183
-
184
-
به همین دلیل است که به آن کامپایل «درست به موقع» (just-in-time) گفته میشود.
185
-
186
-
توجه داشته باشید که اگر `qm(0.5, 10)` را فراخوانی کنید و سپس `qm(0.9, 20)` را دنبال آن بیاورید، کامپایل تنها در اولین فراخوانی انجام میشود.
187
-
188
-
این به این دلیل است که کد کامپایلشده در حافظه نهان ذخیره میشود و در صورت نیاز مجدداً استفاده میشود.
189
-
190
-
به همین دلیل است که در کد بالا، `time3` کوچکتر از `time2` است.
191
-
192
-
## نماد Decorator
193
-
194
-
در کد بالا، نسخه کامپایل شده JIT از `qm` را از طریق فراخوانی ایجاد کردیم
134
+
حالا آن را روی `qm` اعمال میکنیم و تابع جدیدی تولید میکنیم:
195
135
196
136
```{code-cell} ipython3
197
137
qm_numba = jit(qm)
198
138
```
199
139
200
-
در عمل، این معمولاً با استفاده از یک سینتکس جایگزین *decorator* انجام میشود.
201
-
202
-
(ما decoratorها را در یک {doc}`درس جداگانه <python_advanced_features>` بحث میکنیم اما میتوانید جزئیات را در این مرحله رد کنید.)
203
-
204
-
به طور مشخص، برای هدفگیری یک تابع برای کامپایل JIT، میتوانیم `@jit` را قبل از تعریف تابع قرار دهیم.
140
+
تابع `qm_numba` نسخهای از `qm` است که برای کامپایل JIT "هدفگذاری" شده است.
205
141
206
-
در اینجا نحوه انجام این کار برای `qm` آمده است
207
-
208
-
```{code-cell} ipython3
209
-
@jit
210
-
def qm(x0, n):
211
-
x = np.empty(n+1)
212
-
x[0] = x0
213
-
for t in range(n):
214
-
x[t+1] = α * x[t] * (1 - x[t])
215
-
return x
216
-
```
217
-
218
-
این معادل افزودن `qm = jit(qm)` بعد از تعریف تابع است.
219
-
220
-
موارد زیر اکنون از نسخه jitted استفاده میکنند:
142
+
به زودی توضیح خواهیم داد که این به چه معناست.
221
143
222
-
```{code-cell} ipython3
223
-
with qe.Timer(precision=4):
224
-
qm(0.1, 100_000)
225
-
```
144
+
بیایید این نسخه جدید را زمانسنجی کنیم:
226
145
227
146
```{code-cell} ipython3
228
-
with qe.Timer(precision=4):
229
-
qm(0.1, 100_000)
147
+
with qe.Timer() as timer2:
148
+
# Time jitted version
149
+
x = qm_numba(0.1, n)
230
150
```
231
151
232
-
Numba همچنین چندین آرگومان برای decoratorها جهت تسریع محاسبات و ذخیرهسازی cache توابع ارائه میدهد -- [اینجا](https://numba.readthedocs.io/en/stable/user/performance-tips.html) را ببینید.
233
-
234
-
## استنتاج نوع
235
-
236
-
استنتاج موفقیتآمیز نوع، بخش کلیدی کامپایل JIT است.
237
-
238
-
همانطور که میتوانید تصور کنید، استنتاج انواع برای اشیاء ساده Python (مثلاً انواع داده اسکالر ساده مانند اعشاری و صحیح) آسانتر است.
239
-
240
-
Numba همچنین با آرایههای NumPy که انواع به خوبی تعریف شده دارند، به خوبی کار میکند.
241
-
242
-
در یک محیط ایدهآل، Numba میتواند تمام اطلاعات نوع لازم را استنتاج کند.
243
-
244
-
این به آن اجازه میدهد تا کد ماشین بومی تولید کند، بدون نیاز به فراخوانی محیط زمان اجرای Python.
152
+
این یک افزایش سرعت قابل توجه است.
245
153
246
-
وقتی Numba نمیتواند تمام اطلاعات نوع را استنتاج کند، خطا ایجاد میکند.
154
+
در واقع، دفعه بعد و تمام دفعات بعدی سریعتر اجرا میشود، چراکه تابع کامپایل شده و در حافظه قرار دارد:
247
155
248
-
```{note}
249
-
در نسخههای قدیمیتر Numba، دکوراتور `@jit` در صورت عدم توانایی در استنتاج همه انواع، به آرامی به «حالت شیء» بازمیگشت که سرعت چندانی به همراه نداشت. نسخههای فعلی Numba به طور پیشفرض از حالت `nopython` استفاده میکنند، به این معنا که کامپایلر بر استنتاج کامل نوع اصرار دارد و در صورت شکست، خطا ایجاد میکند. اغلب در کدهای دیگر `@njit` را میبینید که صرفاً یک نام مستعار برای `@jit(nopython=True)` است. از آنجا که حالت nopython اکنون پیشفرض است، `@jit` و `@njit` معادل یکدیگرند.
250
-
```
251
-
252
-
به عنوان مثال، در تنظیم (مصنوعی) زیر، Numba قادر به تعیین نوع تابع `mean` هنگام کامپایل تابع `bootstrap` نیست
data = np.array((2.3, 3.1, 4.3, 5.9, 2.1, 3.8, 2.2))
269
-
n_resamples = 10
270
-
271
-
# این کد خطا ایجاد میکند
272
-
try:
273
-
bootstrap(data, mean, n_resamples)
274
-
except Exception as e:
275
-
print(e)
159
+
with qe.Timer() as timer3:
160
+
# Second run
161
+
x = qm_numba(0.1, n)
276
162
```
277
163
278
-
میتوانیم این خطا را در این مورد به راحتی با کامپایل کردن `mean` برطرف کنیم.
164
+
در اینجا میزان افزایش سرعت آمده است:
279
165
280
166
```{code-cell} ipython3
281
-
@jit
282
-
def mean(data):
283
-
return np.mean(data)
284
-
285
-
with qe.Timer():
286
-
bootstrap(data, mean, n_resamples)
167
+
timer1.elapsed / timer3.elapsed
287
168
```
288
169
289
-
## خطرات و محدودیتها
170
+
این یک بهبود قابل توجه با تغییر اندکی در کد اصلی ماست.
290
171
291
-
بیایید برخی یادداشتهای احتیاطی اضافه کنیم.
172
+
بیایید درباره نحوه عملکرد آن بحث کنیم.
292
173
293
-
### محدودیتها
174
+
### چگونگی و زمان عملکرد
294
175
295
-
همانطور که دیدیم، Numba باید اطلاعات نوع را روی تمام متغیرها استنتاج کند تا دستورالعملهای سریع سطح ماشین تولید کند.
176
+
Numba تلاش میکند با استفاده از زیرساخت ارائهشده توسط [پروژه LLVM](https://llvm.org/)، کد ماشین سریعی تولید کند.
296
177
297
-
برای روالهای ساده، Numba انواع را بسیار خوب استنتاج میکند.
178
+
این کار را از طریق استنتاج اطلاعات نوع بهصورت پویا انجام میدهد.
298
179
299
-
برای روالهای بزرگتر، یا برای روالهایی که از کتابخانههای خارجی استفاده میکنند، به راحتی میتواند شکست بخورد.
180
+
(برای بحث درباره انواع، به {doc}`درس <need_for_speed>` قبلی ما درباره محاسبات علمی مراجعه کنید.)
300
181
301
-
از این رو، بهتر است روی تسریع قطعات کوچک و حیاتی کد تمرکز کنید.
302
-
303
-
این به شما عملکرد بسیار بهتری نسبت به پوشاندن برنامههای Python خود با عبارتهای `@jit` میدهد.
304
-
305
-
### یک مشکل: متغیرهای سراسری
306
-
307
-
چیز دیگری که هنگام استفاده از Numba باید مراقب آن باشید در اینجا است.
308
-
309
-
مثال زیر را در نظر بگیرید
310
-
311
-
```{code-cell} ipython3
312
-
a = 1
313
-
314
-
@jit
315
-
def add_a(x):
316
-
return a + x
317
-
318
-
print(add_a(10))
319
-
```
320
-
321
-
```{code-cell} ipython3
322
-
a = 2
323
-
324
-
print(add_a(10))
325
-
```
182
+
ایده اصلی این است:
326
183
327
-
توجه داشته باشید که تغییر global هیچ تأثیری بر مقدار برگشتی توسط تابع نداشت.
184
+
* پایتون بسیار انعطافپذیر است و از این رو میتوانیم تابع qm را با انواع مختلفی فراخوانی کنیم.
185
+
* به عنوان مثال، `x0` میتواند یک آرایه NumPy یا یک لیست باشد، `n` میتواند یک عدد صحیح یا اعشاری باشد، و غیره.
186
+
* این امر، تولید کد ماشین کارآمد *پیش از اجرا* (یعنی قبل از زمان اجرا) را بسیار دشوار میکند.
187
+
* اما وقتی واقعاً تابع را *فراخوانی میکنیم*، مثلاً با اجرای `qm(0.5, 10)`، نوعهای `x0`، `α` و `n` مشخص میشوند.
188
+
* علاوه بر این، نوعهای *سایر متغیرها* در `qm`*به محض مشخص شدن نوعهای ورودی قابل استنتاج هستند*.
189
+
* بنابراین، استراتژی Numba و سایر کامپایلرهای JIT این است که *صبر کنند تا تابع فراخوانی شود*، و سپس کامپایل کنند.
328
190
329
-
وقتی Numba کد ماشین را برای توابع کامپایل میکند، متغیرهای سراسری را به عنوان ثابت برای اطمینان از پایداری نوع درمان میکند.
191
+
این روش "کامپایل درست-به-موقع" نامیده میشود.
330
192
331
-
### ذخیرهسازی کد کامپایلشده
193
+
توجه داشته باشید که اگر `qm_numba(0.5, 10)` را فراخوانی کنید و سپس `qm_numba(0.9, 20)` را اجرا کنید، کامپایل تنها در اولین فراخوانی انجام میشود.
332
194
333
-
به طور پیشفرض، Numba در هر بار شروع یک نشست Python جدید، توابع را مجدداً کامپایل میکند.
195
+
دلیل این امر آن است که کد کامپایلشده در حافظه نهان ذخیره میشود و در صورت نیاز مجدداً استفاده میشود.
334
196
335
-
برای جلوگیری از این سربار، میتوانید `cache=True` را به دکوراتور ارسال کنید:
197
+
به همین دلیل است که در کد بالا، اجرای دوم `qm_numba` سریعتر است.
336
198
337
-
```{code-cell} ipython3
338
-
@jit(cache=True)
339
-
def qm(x0, n):
340
-
x = np.empty(n+1)
341
-
x[0] = x0
342
-
for t in range(n):
343
-
x[t+1] = α * x[t] * (1 - x[t])
344
-
return x
199
+
```{admonition} توضیح
200
+
در عمل، به جای نوشتن `qm_numba = jit(qm)`، معمولاً از نحو *دکوراتور* استفاده میکنیم و `@jit` را قبل از تعریف تابع قرار میدهیم. این معادل افزودن `qm = jit(qm)` پس از تعریف است.
345
201
```
346
202
347
-
این کد کامپایلشده را روی دیسک ذخیره میکند تا نشستهای بعدی بتوانند از مرحله کامپایل صرفنظر کنند.
348
-
349
-
(multithreading)=
350
203
## حلقههای چندنخی در Numba
351
204
352
205
علاوه بر کامپایل JIT، Numba پشتیبانی از محاسبات موازی در CPUها ارائه میدهد.
0 commit comments