وبلاگ رسانگار
با ما حرفه ای باشید

سرور مجازی NVMe

چرا کد پایتون در یک تابع سریعتر اجرا می شود؟

0 6
زمان لازم برای مطالعه: 6 دقیقه


معرفی

پایتون لزوما به دلیل سرعتش شناخته شده نیست، اما موارد خاصی وجود دارد که می تواند به شما کمک کند تا عملکرد بیشتری را از کد خود حذف کنید. با کمال تعجب، یکی از این شیوه ها اجرای کد در یک تابع به جای در محدوده جهانی است. در این مقاله خواهیم دید که چرا کد پایتون در یک تابع سریعتر اجرا می شود و اجرای کد پایتون چگونه کار می کند.

اجرای کد پایتون

برای درک اینکه چرا کد پایتون در یک تابع سریعتر اجرا می شود، ابتدا باید نحوه اجرای کد پایتون را درک کنیم. پایتون یک زبان تفسیری است، به این معنی که کد را خط به خط می خواند و اجرا می کند. هنگامی که پایتون یک اسکریپت را اجرا می کند، ابتدا آن را در بایت کد، یک زبان میانی که به کد ماشین نزدیکتر است، کامپایل می کند و سپس مفسر پایتون این بایت کد را اجرا می کند.

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. همانطور که قبلا دیدیم، این متغیرهای سراسری هستند که اجرای کد را کاهش می دهند، به همین دلیل است که ما آن متغیرها را در این کد جهانی کردیم.

پیشنهاد می‌کنیم بخوانید:  Local و متغیرهای جهانی در پایتون

بهینه سازی عملکرد تابع پایتون

با توجه به اینکه توابع پایتون تمایل دارند سریعتر از کدهای معادل در حوزه جهانی اجرا شوند، ارزش آن را دارد که ببینیم چگونه می توانیم عملکرد عملکرد خود را بیشتر بهینه کنیم.

البته، به دلیل آنچه قبلاً دیدیم، یک استراتژی استفاده از متغیرهای محلی به جای متغیرهای سراسری است. در اینجا یک مثال است:

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

امتیاز شما به این مطلب
دیدگاه شما در خصوص مطلب چیست ؟

آدرس ایمیل شما منتشر نخواهد شد.

لطفا دیدگاه خود را با احترام به دیدگاه های دیگران و با توجه به محتوای مطلب درج کنید