از طریق منوی جستجو مطلب مورد نظر خود در وبلاگ را به سرعت پیدا کنید
چرا کد پایتون در یک تابع سریعتر اجرا می شود؟
سرفصلهای مطلب
معرفی
پایتون لزوما به دلیل سرعتش شناخته شده نیست، اما موارد خاصی وجود دارد که می تواند به شما کمک کند تا عملکرد بیشتری را از کد خود حذف کنید. با کمال تعجب، یکی از این شیوه ها اجرای کد در یک تابع به جای در محدوده جهانی است. در این مقاله خواهیم دید که چرا کد پایتون در یک تابع سریعتر اجرا می شود و اجرای کد پایتون چگونه کار می کند.
اجرای کد پایتون
برای درک اینکه چرا کد پایتون در یک تابع سریعتر اجرا می شود، ابتدا باید نحوه اجرای کد پایتون را درک کنیم. پایتون یک زبان تفسیری است، به این معنی که کد را خط به خط می خواند و اجرا می کند. هنگامی که پایتون یک اسکریپت را اجرا می کند، ابتدا آن را در بایت کد، یک زبان میانی که به کد ماشین نزدیکتر است، کامپایل می کند و سپس مفسر پایتون این بایت کد را اجرا می کند.
def hello_world():
print("Hello, World!")
import dis
dis.dis(hello_world)
2 0 LOAD_GLOBAL 0 (print)
2 LOAD_CONST 1 ('Hello, World!')
4 CALL_FUNCTION 1
6 POP_TOP
8 LOAD_CONST 0 (None)
10 RETURN_VALUE
این دیس ماژول در پایتون عملکرد را جدا می کند hello_world
به بایت کد، همانطور که در بالا مشاهده شد.
توجه داشته باشید: مفسر پایتون یک ماشین مجازی است که بایت کد را اجرا می کند. مفسر پیش فرض پایتون CPython است که به زبان C نوشته شده است. مفسرهای پایتون دیگری مانند Jython (نوشته شده در جاوا)، IronPython (برای دات نت) و PyPy (نوشته شده در پایتون و C) وجود دارد، اما CPython متداول ترین مورد استفاده است. .
چرا کد پایتون در یک تابع سریعتر اجرا می شود؟
یک مثال ساده شده با یک حلقه که در محدوده ای از اعداد تکرار می شود را در نظر بگیرید:
def my_function():
for i in range(100000000):
pass
وقتی این تابع کامپایل می شود، بایت کد ممکن است چیزی شبیه به این باشد:
SETUP_LOOP 20 (to 23)
LOAD_GLOBAL 0 (range)
LOAD_CONST 3 (100000000)
CALL_FUNCTION 1
GET_ITER
FOR_ITER 6 (to 22)
STORE_FAST 0 (i)
JUMP_ABSOLUTE 13
POP_BLOCK
LOAD_CONST 0 (None)
RETURN_VALUE
دستورالعمل کلیدی اینجاست STORE_FAST
، که برای ذخیره متغیر حلقه استفاده می شود i
.
حال بیایید بایت کد را در نظر بگیریم اگر حلقه در سطح بالای یک اسکریپت پایتون باشد:
SETUP_LOOP 20 (to 23)
LOAD_NAME 0 (range)
LOAD_CONST 3 (100000000)
CALL_FUNCTION 1
GET_ITER
FOR_ITER 6 (to 22)
STORE_NAME 1 (i)
JUMP_ABSOLUTE 13
POP_BLOCK
LOAD_CONST 2 (None)
RETURN_VALUE
توجه کنید STORE_NAME
در اینجا از دستورالعمل استفاده می شود STORE_FAST
.
بایت کد STORE_FAST
سریعتر از STORE_NAME
زیرا در یک تابع، متغیرهای محلی در یک آرایه با اندازه ثابت ذخیره می شوند، نه یک فرهنگ لغت. این آرایه مستقیماً از طریق یک شاخص قابل دسترسی است و بازیابی متغیرها را بسیار سریع می کند. اساسا، این فقط جستجوی اشاره گر در لیست و افزایش تعداد مراجع PyObject است، که هر دو عملیات بسیار کارآمد هستند.
از طرف دیگر، متغیرهای سراسری در یک فرهنگ لغت ذخیره می شوند. هنگامی که به یک متغیر سراسری دسترسی دارید، پایتون باید یک جدول هش را جستجو کند، که شامل محاسبه هش و سپس بازیابی مقدار مرتبط با آن است. اگرچه این بهینه سازی شده است، اما همچنان ذاتاً کندتر از جستجوی مبتنی بر فهرست است.
بنچمارک و پروفایل کد پایتون
آیا می خواهید این را برای خودتان تست کنید؟ سعی کنید کد خود را محک بزنید و پروفایل کنید.
بنچمارک و نمایه سازی روش های مهمی در بهینه سازی عملکرد هستند. آنها به شما کمک می کنند بفهمید کد شما چگونه رفتار می کند و گلوگاه ها کجا هستند.
بنچمارک جایی است که کد خود را زمان بندی می کنید تا ببینید چقدر طول می کشد تا اجرا شود. می توانید از برنامه داخلی پایتون استفاده کنید time
ماژول، همانطور که بعدا نشان خواهیم داد، یا از ابزارهای پیچیده تری مانند استفاده کنید زمان.
از سوی دیگر، نمایه سازی نمای دقیق تری از اجرای کد شما ارائه می دهد. این به شما نشان می دهد که کد شما بیشتر زمان خود را کجا می گذراند، کدام توابع فراخوانی می شوند و هر چند وقت یکبار. پایتون داخلی مشخصات یا cProfile برای این کار می توان از ماژول ها استفاده کرد.
در اینجا یکی از راه هایی است که می توانید کد پایتون خود را نمایه کنید:
import cProfile
def loop():
for i in range(10000000):
pass
cProfile.run('loop()')
این یک گزارش دقیق از همه فراخوانی های تابعی که در طول اجرای برنامه انجام شده است را به دست می دهد loop
تابع.
توجه داشته باشید: نمایه سازی می تواند مقدار زیادی سربار به اجرای کد شما اضافه کند، بنابراین زمان اجرای نمایش داده شده توسط نمایه ساز احتمالا بیشتر از زمان اجرای واقعی خواهد بود.
کد معیار در یک عملکرد در مقابل دامنه جهانی
در پایتون، سرعت اجرای کد بسته به محل اجرای کد – در یک تابع یا در محدوده جهانی – می تواند متفاوت باشد. بیایید با استفاده از یک مثال ساده این دو را با هم مقایسه کنیم.
قطعه کد زیر را در نظر بگیرید که فاکتوریل یک عدد را محاسبه می کند:
def factorial(n):
result = 1
for i in range(1, n + 1):
result *= i
return result
حالا بیایید همان کد را اجرا کنیم اما در محدوده جهانی:
n = 20
result = 1
for i in range(1, n + 1):
result *= i
برای محک زدن این دو کد، میتوانیم از timeit
ماژول در پایتون، که یک راه ساده برای زمان بندی بیت های کوچک کد پایتون ارائه می دهد.
import timeit
def benchmark():
start = timeit.default_timer()
factorial(20)
end = timeit.default_timer()
print(end - start)
benchmark()
start = timeit.default_timer()
n = 20
result = 1
for i in range(1, n + 1):
result *= i
end = timeit.default_timer()
print(end - start)
متوجه خواهید شد که کد تابع سریعتر از کد دامنه جهانی اجرا می شود. این به این دلیل است که پایتون به دلایلی که قبلاً در مورد آن صحبت کردیم، کد تابع را سریعتر اجرا می کند.
توجه داشته باشید: اگر شما اجرا کنید benchmark()
تابع و کد دامنه جهانی در یک اسکریپت، کد دامنه جهانی سریعتر اجرا می شود. این به این دلیل است که benchmark()
تابع مقداری سربار به زمان اجرا اضافه می کند و کد جهانی بهینه سازی های داخلی داده می شود. با این حال، اگر آنها را جداگانه اجرا کنید، متوجه خواهید شد که کد تابع سریعتر اجرا می شود.
کد پروفایل در یک تابع در مقابل دامنه جهانی
پایتون یک ماژول داخلی به نام ارائه می کند cProfile
به این منظور. بیایید از آن برای نمایه یک تابع جدید استفاده کنیم، که مجموع مربع ها را در هر دو حوزه محلی و سراسری محاسبه می کند.
import cProfile
def sum_of_squares():
total = 0
for i in range(1, 10000000):
total += i * i
i = None
total = 0
def sum_of_squares_g():
global i
global total
for i in range(1, 10000000):
total += i * i
def profile(func):
pr = cProfile.Profile()
pr.enable()
func()
pr.disable()
pr.print_stats()
print("Function scope:")
profile(sum_of_squares)
print("Global scope:")
profile(sum_of_squares_g)
از نتایج پروفایل، خواهید دید که کد تابع از نظر زمان اجرا کارآمدتر است.
Function scope:
2 function calls in 0.903 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.903 0.903 0.903 0.903 profiler.py:3(sum_of_squares)
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
Global scope:
2 function calls in 1.358 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 1.358 1.358 1.358 1.358 profiler.py:10(sum_of_squares_g)
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
را در نظر می گیریم sum_of_squares_g()
تابع جهانی بودن از آنجایی که از دو متغیر سراسری استفاده می کند، i
و total
. همانطور که قبلا دیدیم، این متغیرهای سراسری هستند که اجرای کد را کاهش می دهند، به همین دلیل است که ما آن متغیرها را در این کد جهانی کردیم.
بهینه سازی عملکرد تابع پایتون
با توجه به اینکه توابع پایتون تمایل دارند سریعتر از کدهای معادل در حوزه جهانی اجرا شوند، ارزش آن را دارد که ببینیم چگونه می توانیم عملکرد عملکرد خود را بیشتر بهینه کنیم.
البته، به دلیل آنچه قبلاً دیدیم، یک استراتژی استفاده از متغیرهای محلی به جای متغیرهای سراسری است. در اینجا یک مثال است:
import time
x = 5
def calculate_power_global():
for i in range(10000000):
y = x ** 2
def calculate_power_local(x):
for i in range(10000000):
y = x ** 2
start = time.time()
calculate_power_global()
end = time.time()
print(f"Execution time with global variable: {end - start} seconds")
start = time.time()
calculate_power_local(x)
end = time.time()
print(f"Execution time with local variable: {end - start} seconds")
در این مثال، calculate_power_local
معمولاً سریعتر از calculate_power_global
، زیرا از یک متغیر محلی به جای جهانی استفاده می کند.
Execution time with global variable: 1.9901456832885742 seconds
Execution time with local variable: 1.9626312255859375 seconds
یکی دیگر از استراتژی های بهینه سازی استفاده از توابع و کتابخانه های داخلی در صورت امکان است. توابع داخلی پایتون در زبان C پیاده سازی می شوند که بسیار سریعتر از پایتون است. به طور مشابه، بسیاری از کتابخانههای پایتون، مانند NumPy و Pandas، در C یا C++ نیز پیادهسازی میشوند که سرعت آنها را از کدهای پایتون معادل میکند.
به عنوان مثال، وظیفه جمع کردن لیستی از اعداد را در نظر بگیرید. برای انجام این کار می توانید یک تابع بنویسید:
def sum_numbers(numbers):
total = 0
for number in numbers:
total += number
return total
با این حال، پایتون داخلی است sum
تابع همان کار را انجام می دهد، اما سریعتر:
numbers = (1, 2, 3, 4, 5)
total = sum(numbers)
زمان بندی این دو قطعه کد را خودتان امتحان کنید و بفهمید کدام یک سریعتر است!
نتیجه
در این مقاله، ما دنیای جالب اجرای کد پایتون را بررسی کردهایم، بهویژه با تمرکز بر این موضوع که چرا کد پایتون زمانی که در یک تابع محصور میشود، سریعتر اجرا میشود. ما به طور مختصر به مفاهیم معیارسنجی و پروفایل نگاه کردیم و مثالهای عملی از نحوه انجام این فرآیندها در یک عملکرد و دامنه جهانی ارائه دادیم.
ما همچنین چند راه برای بهینه سازی عملکرد تابع پایتون شما را مورد بحث قرار دادیم. در حالی که این نکات مطمئناً میتوانند کد شما را سریعتر اجرا کنند، باید از بهینهسازیهای خاصی با دقت استفاده کنید زیرا مهم است که بین خوانایی و قابلیت نگهداری و عملکرد تعادل برقرار کنید.
(برچسبها به ترجمه)# python
منتشر شده در 1402-12-27 08:08:06