profile --- پروفایل‌گیر خالص پایتون

کد منبع: Lib/profile.py


منسوخ شده از نسخه‌ی 3.15, در نسخه‌ی 3.17 حذف خواهد شد.

ماژول profile منسوخ شده است و در پایتون ۳.۱۷ حذف خواهد شد. به جای آن از profiling.tracing استفاده کنید.

ماژول profile یک پیاده‌سازی خالص پایتون از یک پروفایل‌گیر قطعی ارائه می‌دهد. اگرچه این پیاده‌سازی برای درک جزئیات داخلی پروفایل‌گیر یا گسترش رفتار آن از طریق زیرکلاس‌سازی مفید است، اما پیاده‌سازی خالص پایتون آن در مقایسه با ماژول مبتنی بر C و profiling.tracing سربار قابل‌توجهی را ایجاد می‌کند.

برای اکثر وظایف پروفایل‌گیری، از موارد زیر استفاده کنید:

مهاجرت

مهاجرت از profile به profiling.tracing بسیار ساده است. رابط‌های برنامه‌نویسی (APIها) سازگار هستند:

# Old (deprecated)
import profile
profile.run('my_function()')

# New (recommended)
import profiling.tracing
profiling.tracing.run('my_function()')

برای اکثر کدها، جایگزین کردن import profile با import profiling.tracing (و استفاده از profiling.tracing به‌جای profile در سراسر کد) یک مسیر مهاجرت ساده را فراهم می‌کند.

نکته

ماژول cProfile به عنوان یک نام مستعار سازگار با گذشته برای profiling.tracing در دسترس باقی می‌ماند. کدهای موجودی که از import cProfile استفاده می‌کنند، بدون هیچ‌گونه تغییری به کار خود ادامه خواهند داد.

ارجاع به ماژول‌های profile و profiling.tracing

هر دو ماژول profile و profiling.tracing توابع زیر را فراهم می‌کنند:

profile.run(command, filename=None, sort=-1)

این تابع یک آرگومان واحد را که می‌توان آن را به تابع exec() ارسال کرد، و یک نام پرونده اختیاری می‌پذیرد. در همه موارد، این روال کد زیر را اجرا می‌کند:

exec(command, __main__.__dict__, __main__.__dict__)

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

profile.runctx(command, globals, locals, filename=None, sort=-1)

این تابع مشابه run() است، با آرگومان‌های اضافه‌ای برای فراهم‌کردن نگاشت‌های globals و locals برای رشته‌ی command. این روال اجرا می‌کند:

exec(command, globals, locals)

و آمارهای پروفایل‌گیری را همان‌طور که در تابع run() بالا آمده است، جمع‌آوری می‌کند.

class profile.Profile(timer=None, timeunit=0.0, subcalls=True, builtins=True)

این کلاس معمولاً فقط زمانی استفاده می‌شود که به کنترل دقیق‌تری روی پروفایل‌گیری نیاز باشد، فراتر از آنچه تابع profiling.tracing.run() فراهم می‌کند.

می‌توان از طریق آرگومان timer، یک زمان‌سنج سفارشی برای اندازه‌گیری مدت‌زمان اجرای کد فراهم کرد. این باید تابعی باشد که یک عدد واحد را برمی‌گرداند و نشان‌دهنده‌ی زمان جاری است. اگر این عدد یک عدد صحیح باشد، timeunit ضریبی را مشخص می‌کند که مدت‌زمان هر واحد زمان را تعیین می‌کند. برای مثال، اگر زمان‌سنج زمان‌هایی را برگرداند که بر حسب هزاران ثانیه اندازه‌گیری شده‌اند، واحد زمان .001 خواهد بود.

استفاده مستقیم از کلاس Profile امکان قالب‌بندی نتایج پروفایل را بدون نوشتن داده‌های پروفایل در یک پرونده فراهم می‌کند:

import profiling.tracing
import pstats
import io
from pstats import SortKey

pr = profiling.tracing.Profile()
pr.enable()
# ... do something ...
pr.disable()
s = io.StringIO()
sortby = SortKey.CUMULATIVE
ps = pstats.Stats(pr, stream=s).sort_stats(sortby)
ps.print_stats()
print(s.getvalue())

کلاس Profile همچنین می‌تواند به عنوان یک مدیر زمینه (فقط پشتیبانی‌شده در profiling.tracing و نه در ماژول منسوخ profile؛ نگاه کنید به انواع مدیر زمینه) استفاده شود:

import profiling.tracing

with profiling.tracing.Profile() as pr:
    # ... do something ...

    pr.print_stats()

تغییر یافته در نسخه‌ی 3.8: پشتیبانی از مدیر زمینه افزوده شد.

enable()

شروع جمع‌آوری داده‌های پروفایل‌گیری. فقط در profiling.tracing.

disable()

توقف جمع‌آوری داده‌های پروفایل‌گیری. فقط در profiling.tracing.

create_stats()

جمع‌آوری داده‌های پروفایل‌گیری را متوقف کنید و نتایج را به‌صورت داخلی به‌عنوان پروفایل فعلی ثبت کنید.

print_stats(sort=-1)

یک شیء Stats بر اساس پروفایل فعلی ایجاد کنید و نتایج را در stdout چاپ کنید.

پارامتر sort ترتیب مرتب‌سازی آمارهای نمایش‌داده‌شده را مشخص می‌کند. این پارامتر یک کلید منفرد یا تاپلی از کلیدها را برای فعال‌سازی مرتب‌سازی چندسطحی می‌پذیرد، درست مانند pstats.Stats.sort_stats().

اضافه شده در نسخه‌ی 3.13: print_stats() اکنون یک تاپل از کلیدها را می‌پذیرد.

dump_stats(filename)

نتایج پروفایل فعلی را در filename بنویسید.

run(cmd)

cmd را از طریق exec() پروفایل کنید

runctx(cmd, globals, locals)

cmd را از طریق exec() با محیط سراسری و محلی مشخص‌شده پروفایل کنید

runcall(func, /, *args, **kwargs)

func(*args, **kwargs) را پروفایل می‌کند

توجه داشته باشید که پروفایل‌گیری تنها در صورتی کار می‌کند که فرمان/تابع فراخوانی‌شده واقعاً بازگشت داشته باشد. اگر مفسر خاتمه یابد (مثلاً از طریق فراخوانی sys.exit() در حین اجرای فرمان/تابع فراخوانی‌شده)، هیچ نتیجه‌ای از پروفایل‌گیری چاپ نخواهد شد.

تفاوت‌ها با profiling.tracing

ماژول profile در چند مورد با profiling.tracing متفاوت است:

سربار بالاتر. پیاده‌سازی خالص پایتون به‌طور قابل‌توجهی کندتر از پیاده‌سازی C است و این امر آن را برای پروفایل‌گیری برنامه‌های طولانی‌مدت یا کدهای حساس به عملکرد نامناسب می‌سازد.

پشتیبانی از کالیبراسیون. ماژول profile از کالیبراسیون برای جبران سربار پروفایل‌گیری پشتیبانی می‌کند. این کار در profiling.tracing نیازی نیست زیرا پیاده‌سازی C سربار ناچیزی دارد.

تایمرهای سفارشی. هر دو ماژول از تایمرهای سفارشی پشتیبانی می‌کنند، اما profile توابع تایمری را می‌پذیرد که تاپل برمی‌گردانند (مانند os.times())، در حالی که profiling.tracing به تابعی نیاز دارد که یک عدد واحد برگرداند.

زیرکلاس‌سازی. پیاده‌سازی خالص پایتون برای زیرکلاس‌سازی و گسترش جهت رفتار سفارشی پروفایل‌گیری آسان‌تر است.

پروفایل‌گیری قطعی (deterministic profiling) چیست؟

پروفایل‌گیری قطعی برای بازتاب این واقعیت در نظر گرفته شده است که تمامی رویدادهای function call، function return و exception نظارت می‌شوند و زمان‌بندی‌های دقیقی برای بازه‌های بین این رویدادها (زمانی که کد کاربر در حال اجراست) انجام می‌شود. در مقابل، پروفایل‌گیری آماری (که توسط ماژول profiling.sampling ارائه می‌شود) به‌طور دوره‌ای اشاره‌گر دستورالعمل مؤثر را نمونه‌برداری می‌کند و استنباط می‌کند که زمان صرف کجا می‌شود. روش دوم به‌طور سنتی سربار کمتری دارد (چرا که نیازی به ابزارگذاری کد نیست)، اما تنها نشانه‌های نسبی از میزان صرف زمان را ارائه می‌دهد.

در پایتون، از آن‌جا که در حین اجرا یک مفسر فعال است، برای انجام پروفایل‌گیری قطعی (deterministic profiling) نیازی به وجود کد ابزاربندی‌شده (instrumented code) نیست. پایتون به‌طور خودکار یک hook (کال‌بک اختیاری) برای هر رویداد فراهم می‌کند. علاوه بر این، ماهیت تفسیری پایتون معمولاً آن‌قدر سربار زیادی به اجرا اضافه می‌کند، به‌طوری که پروفایل‌گیری قطعی معمولاً فقط سربار پردازشی اندکی در برنامه‌های کاربردی معمولی اضافه می‌کند. نتیجه این است که پروفایل‌گیری قطعی چندان پرهزینه نیست، اما آمار گسترده‌ای از زمان اجرا درباره اجرای یک برنامه پایتون ارائه می‌دهد.

می‌توان از آمار تعداد فراخوانی‌ها برای شناسایی اشکال‌های کد (تعداد فراخوانی‌های غیرمنتظره) و برای شناسایی نقاط ممکن برای بسط درون‌خطی (inline-expansion) (تعداد فراخوانی‌های بالا) استفاده کرد. می‌توان از آمار زمان داخلی برای شناسایی «حلقه‌های داغ» که باید با دقت بهینه‌سازی شوند، استفاده کرد. باید از آمار زمان تجمعی برای شناسایی خطاهای سطح بالا در انتخاب الگوریتم‌ها استفاده شود. توجه داشته باشید که مدیریت غیرمعمول زمان‌های تجمعی در این پروفایل‌گیر به شما امکان می‌دهد آمار پیاده‌سازی‌های بازگشتی الگوریتم‌ها را به‌طور مستقیم با پیاده‌سازی‌های تکراری مقایسه کنید.

محدودیت‌ها

یکی از محدودیت‌ها به دقت اطلاعات زمان‌بندی مربوط می‌شود. در پروفایل‌گیرهای قطعی (deterministic profilers) مشکلی بنیادین در مورد دقت وجود دارد. واضح‌ترین محدودیت این است که «ساعت» زیربنایی تنها با نرخی (معمولاً) حدود ۰٫۰۰۱ ثانیه تیک می‌زند. بنابراین، هیچ اندازه‌گیری‌ای دقیق‌تر از ساعت زیربنایی نخواهد بود. اگر تعداد کافی اندازه‌گیری انجام شود، «خطا» تمایل دارد به‌طور میانگین خنثی شود. متأسفانه، حذف این خطای نخست، منبع دوم خطا را به وجود می‌آورد.

دومین مشکل این است که از لحظه‌ای که یک رویداد ارسال می‌شود تا زمانی که فراخوانی پروفایل‌گیر برای گرفتن زمان واقعاً وضعیت ساعت را دریافت می‌کند، «مدتی طول می‌کشد». به‌طور مشابه، هنگام خروج از هندلر رویداد پروفایل‌گیر، از زمانی که مقدار ساعت گرفته شده (و سپس ذخیره شده است) تا زمانی که کد کاربر دوباره در حال اجرا است، تأخیر مشخصی وجود دارد. در نتیجه، توابعی که بارها فراخوانی می‌شوند یا توابع زیادی را فراخوانی می‌کنند، معمولاً این خطا را انباشته می‌کنند. خطایی که به این شکل انباشته می‌شود، معمولاً کمتر از دقت ساعت است (کمتر از یک تیک ساعت)، اما می‌تواند انباشته شود و بسیار قابل‌توجه شود.

این مسئله در ماژول منسوخ‌شده profile در مقایسه با ماژول کم‌سربار profiling.tracing اهمیت بیشتری دارد. به همین دلیل، profile ابزاری برای کالیبره‌کردن خود برای یک پلتفرم مشخص فراهم می‌کند تا بتوان این خطا را به روش احتمالی (به طور میانگین) برطرف کرد. پس از کالیبره‌شدن پروفایل‌گیر، دقت آن بیشتر خواهد شد (به معنای کمترین مربعات)، اما گاهی اوقات اعداد منفی تولید خواهد کرد (زمانی که تعداد فراخوانی‌ها به طرز استثنایی کم باشد و قوانین احتمال علیه شما کار کنند :-)). از اعداد منفی در پروفایل نگران نشوید. آن‌ها فقط زمانی باید ظاهر شوند که پروفایل‌گیر خود را کالیبره کرده باشید و نتایج در واقع بهتر از حالت بدون کالیبره باشند.

کالیبراسیون

پروفایل‌گیر ماژول profile یک مقدار ثابت را از زمان پردازش هر رویداد کم می‌کند تا سربارِ فراخوانی تابع زمان و ذخیره‌سازی نتایج را جبران کند. به طور پیش‌فرض، این مقدار ثابت 0 است. از روش زیر می‌توان برای به دست آوردن مقدار ثابت بهتری برای یک پلتفرم مشخص استفاده کرد (به محدودیت‌ها مراجعه کنید).

import profile
pr = profile.Profile()
for i in range(5):
    print(pr.calibrate(10000))

این متد تعداد فراخوانی‌های پایتون مشخص‌شده توسط آرگومان را یک بار به‌طور مستقیم و بار دیگر تحت پروفایل‌گیر اجرا می‌کند و زمان هر دو را اندازه‌گیری می‌کند. سپس سربار پنهان به‌ازای هر رویداد پروفایل‌گیر را محاسبه می‌کند و آن را به‌عنوان یک عدد اعشاری بازمی‌گرداند. برای مثال، روی یک Intel Core i5 با فرکانس ۱٫۸ گیگاهرتز که macOS را اجرا می‌کند و با استفاده از time.process_time() پایتون به‌عنوان زمان‌سنج، عدد جادویی حدود ۴٫۰۴e-۶ است.

هدف از این تمرین، دستیابی به نتیجه‌ای نسبتاً پایدار است. اگر رایانه‌ی شما بسیار سریع است، یا تابع زمان‌سنج شما دقت زمانی پایینی دارد، ممکن است لازم باشد ۱۰۰۰۰۰، یا حتی ۱۰۰۰۰۰۰، را پاس بدهید تا نتایج پایداری بگیرید.

هنگامی که پاسخ سازگاری دارید، ۳ روش برای استفاده از آن وجود دارد:

import profile

# 1. Apply computed bias to all Profile instances created hereafter.
profile.Profile.bias = your_computed_bias

# 2. Apply computed bias to a specific Profile instance.
pr = profile.Profile()
pr.bias = your_computed_bias

# 3. Specify computed bias in instance constructor.
pr = profile.Profile(bias=your_computed_bias)

اگر حق انتخاب دارید، بهتر است ثابت کوچک‌تری را انتخاب کنید، و در این صورت نتایج شما «کمتر» به‌صورت منفی در آمار پروفایل نمایش داده می‌شوند.

استفاده از زمان‌سنج سفارشی

اگر می‌خواهید نحوه تعیین زمان جاری را تغییر دهید (برای مثال، برای اجبار به استفاده از زمان دیواری یا زمان سپری‌شده فرایند)، تابع زمان‌سنجی مورد نظر خود را به سازنده کلاس Profile ارسال کنید:

pr = profile.Profile(your_time_func)

سپس پروفایل‌گیر حاصل، your_time_func را فراخوانی خواهد کرد. بسته به اینکه از profile.Profile یا profiling.tracing.Profile استفاده می‌کنید، مقدار بازگشتی your_time_func به شکل متفاوتی تفسیرشده خواهد شد:

profile.Profile

your_time_func باید یک عدد زمانی واحد، یا فهرستی از اعداد را برگرداند که مجموع آن‌ها زمان جاری است (مانند آنچه os.times() برمی‌گرداند). اگر تابع یک عدد زمانی واحد برگرداند، یا فهرست اعداد برگردانده‌شده طول ۲ داشته باشد، نسخه‌ای به‌ویژه سریع از روال اعزام (dispatch routine) دریافت خواهید کرد.

توجه داشته باشید که باید کلاس پروفایل‌گیر را برای تابع زمان‌سنجی که انتخاب می‌کنید کالیبره کنید (به کالیبراسیون مراجعه کنید). در بیشتر ماشین‌ها، زمان‌سنجی که تنها یک مقدار عدد صحیح برمی‌گرداند، بهترین نتایج را از نظر سربار کم در حین پروفایل‌کردن فراهم می‌کند. (os.times() نسبتاً بد است، زیرا یک تاپل از مقادیر ممیز شناور برمی‌گرداند). اگر می‌خواهید یک زمان‌سنج بهتر را به تمیزترین روش جایگزین کنید، یک کلاس مشتق کنید و یک متد اعزام (dispatch) جایگزین را که فراخوانی زمان‌سنج شما را به بهترین شکل مدیریت می‌کند، به‌همراه ثابت کالیبراسیون مناسب، به‌صورت سخت‌کد (hardwire) پیاده‌سازی کنید.

profiling.tracing.Profile

your_time_func باید یک عدد واحد برگرداند. اگر این تابع اعداد صحیح برگرداند، همچنین می‌توانید سازنده‌ی کلاس را با یک آرگومان دوم برای مشخص کردن مدت‌زمان واقعی یک واحد زمان فراخوانی کنید. برای مثال، اگر your_integer_time_func مقادیر زمانی را برگرداند که بر حسب هزاران ثانیه اندازه‌گیری شده‌اند، باید نمونه‌ی Profile را به‌صورت زیر بسازید:

pr = profiling.tracing.Profile(your_integer_time_func, 0.001)

از آنجا که کلاس profiling.tracing.Profile قابل کالیبره‌شدن نیست، توابع تایمر سفارشی باید با احتیاط استفاده شوند و تا حد امکان سریع باشند. برای کسب بهترین نتایج با یک تایمر سفارشی، ممکن است لازم باشد آن را در کد C ماژول داخلی _lsprof به صورت سخت‌کد شده قرار دهید.

پایتون 3.3 چندین تابع جدید به time اضافه می‌کند که می‌توان از آن‌ها برای اندازه‌گیری‌های دقیق زمان فرایند یا زمان دیواری استفاده کرد. برای مثال، time.perf_counter() را ببینید.

همچنین ببینید

profiling

نمای کلی ابزارهای پروفایل‌گیری پایتون.

profiling.tracing

جایگزین پیشنهادی برای این ماژول.

pstats

تحلیل آماری و قالب‌بندی برای داده‌های پروفایل.