پروفایل‌گیرهای پایتون

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


آشنایی با پروفایل‌گیرها

cProfile و profile، پروفایل‌گیری قطعی (deterministic profiling) <deterministic profiling> برنامه‌های پایتون را فراهم می‌کنند. یک پروفایل <profile> مجموعه‌ای از آمارها است که توصیف می‌کند بخش‌های مختلف برنامه چند بار و برای چه مدت اجرا شده‌اند. این آمارها را می‌توان از طریق ماژول pstats در قالب گزارش قالب‌بندی کرد.

کتابخانه استاندارد پایتون دو پیاده‌سازی متفاوت از یک رابط پروفایل‌گیری یکسان ارائه می‌دهد:

  1. cProfile برای بیشتر کاربران توصیه می‌شود؛ این یک افزونه C با سربار معقول است که آن را برای پروفایل‌گیری برنامه‌های با اجرای طولانی مناسب می‌سازد. بر پایه lsprof، با مشارکت Brett Rosen و Ted Czotter.

  2. profile، یک ماژول پایتون خالص است که رابط آن توسط cProfile تقلید شده است، اما سربار قابل‌توجهی به برنامه‌های پروفایل‌شده (profiled) اضافه می‌کند. اگر می‌خواهید پروفایل‌گیر (profiler) را به شکلی گسترش دهید، ممکن است این کار با این ماژول آسان‌تر باشد. در ابتدا توسط Jim Roskind طراحی و نوشته شده است.

توجه

ماژول‌های پروفایل‌گیر طراحی شده‌اند تا پروفایل اجرای یک برنامه مشخص را فراهم کنند، نه برای اهداف محک‌زنی (برای آن، timeit برای نتایج نسبتاً دقیق وجود دارد). این موضوع به‌ویژه در مورد محک‌زدن کد پایتون در برابر کد C صدق می‌کند: پروفایل‌گیرها سرباری برای کد پایتون ایجاد می‌کنند، اما برای توابع سطح C نه، و بنابراین کد C سریع‌تر از هر کد پایتونی به نظر خواهد رسید.

راهنمای کاربری فوری

این بخش برای کاربرانی فراهم شده است که «نمی‌خواهند راهنما را بخوانند». این بخش مروری بسیار کوتاه ارائه می‌دهد و به کاربر امکان می‌دهد تا به‌سرعت پروفایل‌گیری را روی یک برنامه موجود انجام دهد.

برای پروفایل‌گیری تابعی که یک آرگومان می‌گیرد، می‌توانید به این صورت عمل کنید:

import cProfile
import re
cProfile.run('re.compile("foo|bar")')

(اگر cProfile در سیستم شما در دسترس نیست، به‌جای آن از profile استفاده کنید.)

عملیات بالا re.compile() را اجرا کرده و نتایج پروفایل را مانند زیر چاپ می‌کند:

      214 function calls (207 primitive calls) in 0.002 seconds

Ordered by: cumulative time

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
     1    0.000    0.000    0.002    0.002 {built-in method builtins.exec}
     1    0.000    0.000    0.001    0.001 <string>:1(<module>)
     1    0.000    0.000    0.001    0.001 __init__.py:250(compile)
     1    0.000    0.000    0.001    0.001 __init__.py:289(_compile)
     1    0.000    0.000    0.000    0.000 _compiler.py:759(compile)
     1    0.000    0.000    0.000    0.000 _parser.py:937(parse)
     1    0.000    0.000    0.000    0.000 _compiler.py:598(_code)
     1    0.000    0.000    0.000    0.000 _parser.py:435(_parse_sub)

خط نخست نشان می‌دهد که ۲۱۴ فراخوانی پایش شده‌اند. از میان آن فراخوانی‌ها، ۲۰۷ مورد اولیه <primitive> بودند، به این معنا که فراخوانی از طریق بازگشت ایجاد نشده است. خط بعدی: Ordered by: cumulative time نشان می‌دهد که خروجی بر اساس مقادیر cumtime مرتب شده است. عنوان ستون‌ها شامل:

ncalls

برای تعداد فراخوانی‌ها.

tottime

برای کل زمان صرف‌شده در تابع داده‌شده (و به‌جز زمان صرف‌شده در فراخوانی‌های توابع فرعی)

percall

خارج‌قسمت tottime بر ncalls است

زمان تجمعی (cumtime)

زمان تجمعی صرف‌شده در این تابع و همه زیرتوابع (از فراخوانی تا خروج) است. این رقم حتی برای توابع بازگشتی نیز دقیق است.

percall

خارج‌قسمت cumtime بر فراخوانی‌های اولیه است

filename:lineno(function)

داده‌های مربوط به هر تابع را فراهم می‌کند

هنگامی که دو عدد در ستون اول وجود داشته باشد (برای مثال 3/1)، به این معناست که تابع بازگشتی فراخوانی شده است. مقدار دوم تعداد فراخوانی‌های اولیه (primitive calls) و مقدار اول تعداد کل فراخوانی‌هاست. توجه داشته باشید که وقتی تابع بازگشتی فراخوانی نشود، این دو مقدار یکسان هستند و فقط یک عدد چاپ می‌شود.

به‌جای چاپ خروجی در پایان اجرای پروفایل، می‌توانید با مشخص کردن نام پرونده برای تابع run()، نتایج را در یک پرونده ذخیره کنید:

import cProfile
import re
cProfile.run('re.compile("foo|bar")', 'restats')

کلاس pstats.Stats نتایج پروفایل را از یک پرونده می‌خواند و آن‌ها را به روش‌های مختلفی قالب‌بندی می‌کند.

همچنین می‌توان پرونده‌های cProfile و profile را به‌عنوان یک اسکریپت برای پروفایل‌گیری اسکریپتی دیگر فراخوانی کرد. برای مثال:

python -m cProfile [-o output_file] [-s sort_order] (-m module | myscript.py)
-o <output_file>

نتایج پروفایل را به‌جای stdout در یک پرونده می‌نویسد.

-s <sort_order>

یکی از مقادیر مرتب‌سازی sort_stats() را برای مرتب‌سازی خروجی مشخص می‌کند. این مورد فقط زمانی اعمال می‌شود که -o ارائه نشده باشد.

-m <module>

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

اضافه شده در نسخه‌ی 3.7: گزینه -m به cProfile افزوده شد.

اضافه شده در نسخه‌ی 3.8: گزینه -m به profile افزوده شد.

کلاس Stats در ماژول pstats دارای متدهای مختلفی برای دستکاری و چاپ داده‌های ذخیره‌شده در پرونده نتایج پروفایل است:

import pstats
from pstats import SortKey
p = pstats.Stats('restats')
p.strip_dirs().sort_stats(-1).print_stats()

متد strip_dirs() مسیر اضافی را از تمام نام‌های ماژول حذف کرد. متد sort_stats() تمام ورودی‌ها را بر اساس رشته‌ی استاندارد ماژول/خط/نام که چاپ می‌شود مرتب کرد. متد print_stats() تمام آمار را چاپ کرد. ممکن است بخواهید فراخوانی‌های مرتب‌سازی زیر را امتحان کنید:

p.sort_stats(SortKey.NAME)
p.print_stats()

نخستین فراخوانی در واقع فهرست را بر اساس نام تابع مرتب می‌کند و دومین فراخوانی آمار را چاپ می‌کند. موارد زیر چند فراخوانی جالب برای آزمایش هستند:

p.sort_stats(SortKey.CUMULATIVE).print_stats(10)

این دستور پروفایل را بر اساس زمان تجمعی در یک تابع مرتب می‌کند و سپس فقط ۱۰ خط مهم را چاپ می‌کند. اگر می‌خواهید بفهمید کدام الگوریتم‌ها زمان‌بر هستند، باید از خط بالا استفاده کنید.

اگر می‌خواستید ببینید چه توابعی حلقه‌های زیادی اجرا می‌کردند و زمان زیادی می‌بردند، این کار را می‌کردید:

p.sort_stats(SortKey.TIME).print_stats(10)

برای مرتب‌سازی بر اساس زمان سپری‌شده در هر تابع، و سپس چاپ آمار برای ده تابع برتر.

همچنین ممکن است بخواهید این را نیز امتحان کنید:

p.sort_stats(SortKey.FILENAME).print_stats('__init__')

این کار تمام آمارها را بر اساس نام پرونده مرتب می‌کند، و سپس فقط آمار متدهای init کلاس را چاپ می‌کند (زیرا در نام آن‌ها __init__ وجود دارد). به‌عنوان یک مثال نهایی، می‌توانید امتحان کنید:

p.sort_stats(SortKey.TIME, SortKey.CUMULATIVE).print_stats(.5, 'init')

این خط، آمار را با کلید اصلیِ زمان و کلید ثانویه‌ی زمان تجمعی مرتب می‌کند و سپس برخی از آمارها را چاپ می‌کند. به‌طور مشخص، ابتدا فهرست به ۵۰٪ اندازه‌ی اصلی خود (مربوط به .5) کاهش می‌یابد، سپس فقط ردیفهای شامل init نگه داشته می‌شوند و آن زیرفهرستِ تودرتو چاپ می‌شود.

اگر می‌خواستید بدانید چه توابعی توابع بالا را فراخوانی کرده‌اند، اکنون می‌توانید (p هنوز بر اساس آخرین معیار مرتب شده است) این کار را انجام دهید:

p.print_callers(.5, 'init')

و شما فهرستی از فراخواننده‌های هر یک از توابع فهرست‌شده را دریافت خواهید کرد.

اگر قابلیت بیشتری می‌خواهید، باید راهنما را بخوانید یا حدس بزنید توابع زیر چه کاری انجام می‌دهند:

p.print_callees()
p.add('restats')

هنگامی که به‌عنوان یک اسکریپت اجرا شود، ماژول pstats یک مرورگر آمار برای خواندن و بررسی برون‌ریزی‌های پروفایل (profile dumps) است. این ماژول دارای یک رابط خط‌محور ساده (پیاده‌سازی‌شده با استفاده از cmd) و راهنمای تعاملی است.

مرجع ماژول‌های profile و cProfile

هر دو ماژول profile و cProfile توابع زیر را ارائه می‌دهند:

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)

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

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

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

import cProfile, pstats, io
from pstats import SortKey
pr = cProfile.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 همچنین می‌تواند به‌عنوان یک مدیر زمینه استفاده شود (فقط در ماژول cProfile پشتیبانی می‌شود. به Context Manager Types مراجعه کنید):

import cProfile

with cProfile.Profile() as pr:
    # ... do something ...

    pr.print_stats()

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

enable()

جمع‌آوری داده‌های پروفایل‌گیری را آغاز کنید. فقط در cProfile.

disable()

توقف جمع‌آوری داده‌های پروفایل‌سازی. فقط در cProfile.

create_stats()

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

print_stats(sort=-1)

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

پارامتر sort ترتیب مرتب‌سازی آمار نمایش‌یافته را تعیین می‌کند. این پارامتر یک کلید واحد یا تاپلی از کلیدها را برای فعال‌سازی مرتب‌سازی چندسطحی می‌پذیرد، همان‌طور که در 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() در حین اجرای فرمان/تابع فراخوانی‌شده)، هیچ نتیجه‌ای از پروفایل‌گیری چاپ نخواهد شد.

کلاس Stats

تحلیل داده‌های پروفایل‌گیر با استفاده از کلاس Stats انجام می‌شود.

class pstats.Stats(*filenames or profile, stream=sys.stdout)

سازنده‌ی این کلاس، نمونه‌ای از «شیء آماری» را از یک filename (یا فهرستی از نام پرونده‌ها) یا از یک نمونه‌ی Profile ایجاد می‌کند. خروجی به جریان مشخص‌شده توسط stream چاپ می‌شود.

پرونده انتخاب‌شده توسط سازنده‌ی بالا باید به‌وسیله‌ی نسخه‌ی متناظر profile یا cProfile ایجاد شده باشد. به‌طور مشخص، هیچ سازگاری پرونده‌ای با نسخه‌های آینده‌ی این پروفایل‌گیر تضمین نمی‌شود، و هیچ سازگاری با پرونده‌های تولیدشده توسط پروفایل‌گیرهای دیگر، یا همان پروفایل‌گیر اجراشده روی یک سیستم‌عامل دیگر وجود ندارد. اگر چندین پرونده ارائه شود، همه‌ی آمارهای مربوط به توابع یکسان با هم ادغام می‌شوند، تا بتوان نمای کلی از چندین فرایند را در یک گزارش واحد در نظر گرفت. اگر نیاز باشد پرونده‌های اضافی با داده‌های موجود در یک شیء Stats ترکیب شوند، می‌توان از متد add() استفاده کرد.

به جای خواندن داده‌های پروفایل از یک پرونده، می‌توان از یک شیء cProfile.Profile یا profile.Profile به‌عنوان منبع داده‌های پروفایل استفاده کرد.

اشیای Stats دارای متدهای زیر هستند:

strip_dirs()

این متد برای کلاس Stats تمام اطلاعات ابتدایی مسیر را از نام پرونده‌ها حذف می‌کند. این کار برای کاهش اندازه خروجی چاپی جهت جا شدن در (نزدیک به) ۸۰ ستون بسیار مفید است. این متد شیء را تغییر می‌دهد، و اطلاعات حذف‌شده از دست می‌رود. پس از انجام عملیات حذف، فرض می‌شود که آیتم‌های شیء در ترتیبی «تصادفی» قرار دارند، همان‌طور که درست پس از مقداردهی اولیه و بارگذاری شیء بودند. اگر strip_dirs() باعث شود دو نام تابع غیرقابل تمایز شوند (روی یک خط از یک نام پرونده باشند و نام تابع یکسانی داشته باشند)، آمار این دو آیتم در یک آیتم واحد انباشته می‌شود.

add(*filenames)

این متد از کلاس Stats، اطلاعات پروفایل‌گیری اضافی را در شیء پروفایل‌گیری جاری انباشته می‌کند. آرگومان‌های آن باید به نام پرونده‌هایی ارجاع دهند که توسط نسخه‌ی متناظر profile.run() یا cProfile.run() ایجاد شده‌اند. آمار توابعی که نام یکسان دارند (از نظر: پرونده، خط، نام) به‌طور خودکار در آمار یک تابع واحد انباشته می‌شود.

dump_stats(filename)

داده‌های بارگذاری‌شده در شیء Stats را در پرونده‌ای به نام filename ذخیره کنید. در صورتی که پرونده وجود نداشته باشد، ایجاد می‌شود و اگر از قبل وجود داشته باشد، بازنویسی می‌شود. این معادل متدی با همین نام در کلاس‌های profile.Profile و cProfile.Profile است.

sort_stats(*keys)

این متد شیء Stats را با مرتب‌سازی آن بر اساس معیارهای ارائه‌شده تغییر می‌دهد. آرگومان می‌تواند یک رشته یا یک شمارش (enum) از SortKey باشد که مبنای مرتب‌سازی را مشخص می‌کند (مثال: 'time'، 'name'، SortKey.TIME یا SortKey.NAME). آرگومان شمارش‌های (enum) SortKey نسبت به آرگومان رشته‌ای این مزیت را دارد که پایدارتر و کمتر مستعد خطا است.

وقتی بیش از یک کلید ارائه شود، کلیدهای اضافی به‌عنوان معیارهای ثانویه در صورتی استفاده می‌شوند که در تمام کلیدهای انتخاب‌شده پیش از آن‌ها برابری وجود داشته باشد. برای مثال، sort_stats(SortKey.NAME, SortKey.FILE) تمام ورودی‌ها را بر اساس نام تابعشان مرتب می‌کند و تمام تساوی‌ها (نام‌های تابع یکسان) را با مرتب‌سازی بر اساس نام پرونده برطرف می‌کند.

برای آرگومان رشته‌ای، می‌توان از مخفف‌ها برای هر یک از نام‌های کلید استفاده کرد، مشروط بر این‌که مخفف بدون ابهام باشد.

موارد زیر رشته و SortKey معتبر هستند:

آرگومان رشته‌ای معتبر

آرگومان enum معتبر

معنی

'calls'

SortKey.CALLS

تعداد فراخوانی‌ها

'cumulative'

SortKey.CUMULATIVE

زمان تجمعی

'cumtime'

ناموجود

زمان تجمعی

'file'

ناموجود

نام پرونده

'filename'

SortKey.FILENAME

نام پرونده

'module'

ناموجود

نام پرونده

'ncalls'

ناموجود

تعداد فراخوانی‌ها

'pcalls'

SortKey.PCALLS

تعداد فراخوانی‌های اولیه

'line'

SortKey.LINE

شماره خط

'name'

SortKey.NAME

نام تابع

'nfl'

SortKey.NFL

نام/پرونده/خط

'stdname'

SortKey.STDNAME

نام استاندارد

'time'

SortKey.TIME

زمان داخلی

'tottime'

ناموجود

زمان داخلی

توجه داشته باشید که تمام مرتب‌سازی‌ها بر اساس آمار به ترتیب نزولی هستند (زمان‌برترین آیتم‌ها در ابتدا قرار می‌گیرند)، در حالی که مرتب‌سازی‌های نام، پرونده و شماره خط به ترتیب صعودی (الفبایی) هستند. تفاوت ظریف بین SortKey.NFL و SortKey.STDNAME این است که نام استاندارد، مرتب‌سازی نام به همان صورت چاپ‌شده است؛ این بدان معناست که شماره‌های خط تعبیه‌شده به طرز عجیبی مقایسه می‌شوند. برای مثال، سطرهای ۳، ۲۰ و ۴۰ (اگر نام پرونده‌ها یکسان باشد) با ترتیب رشته‌ای به صورت ۲۰، ۳ و ۴۰ ظاهر می‌شوند. در مقابل، SortKey.NFL شماره‌های خط را به صورت عددی مقایسه می‌کند. در واقع، sort_stats(SortKey.NFL) همان sort_stats(SortKey.NAME, SortKey.FILENAME, SortKey.LINE) است.

به دلایل سازگاری با نسخه‌های قدیمی، آرگومان‌های عددی -1، 0، 1 و 2 مجاز هستند. این مقادیر به‌ترتیب به‌عنوان 'stdname'، 'calls'، 'time' و 'cumulative' تفسیر می‌شوند. اگر از این قالب قدیمی (عددی) استفاده شود، تنها یک کلید مرتب‌سازی (کلید عددی) استفاده خواهد شد و آرگومان‌های اضافی به‌صورت بی‌صدا نادیده گرفته می‌شوند.

اضافه شده در نسخه‌ی 3.7: شمارش SortKey افزوده شد.

reverse_order()

این متد برای کلاس Stats، ترتیب فهرست پایه درون شیء را معکوس می‌کند. توجه داشته باشید که به‌طور پیش‌فرض، ترتیب صعودی یا نزولی به‌درستی بر اساس کلید مرتب‌سازی انتخابی انتخاب می‌شود.

print_stats(*restrictions)

این متد از کلاس Stats، گزارشی را همان‌طور که در تعریف profile.run() شرح داده شده است، چاپ می‌کند.

ترتیب چاپ بر اساس آخرین عملیات sort_stats() انجام‌شده روی شیء است (با توجه به ملاحظات موجود در add() و strip_dirs()).

می‌توان از آرگومان‌های داده‌شده (در صورت وجود) برای محدود کردن فهرست به آیتم‌های مهم استفاده کرد. در ابتدا، فهرست به‌عنوان مجموعه‌ی کاملی از توابع پروفایل‌شده در نظر گرفته می‌شود. هر محدودیت یا یک عدد صحیح است (برای انتخاب تعدادی از سطرهای)، یا یک کسر اعشاری بین ۰٫۰ و ۱٫۰ شامل هر دو (برای انتخاب درصدی از سطرهای)، یا رشته‌ای است که به‌عنوان عبارت باقاعده تفسیر می‌شود (برای تطبیق الگو با نام استانداردی که چاپ می‌شود). اگر چندین محدودیت ارائه شود، آن‌ها به‌ترتیب اعمال می‌شوند. برای مثال:

print_stats(.1, 'foo:')

ابتدا چاپ را به ۱۰٪ نخست فهرست محدود می‌کند، و سپس فقط توابعی را چاپ می‌کند که بخشی از نام پرونده .*foo: بودند. در مقابل، دستور:

print_stats('foo:', .1)

فهرست را به همه توابعی که نام پرونده‌هایشان .*foo: است محدود می‌کند، و سپس تنها ۱۰٪ نخست آن‌ها را چاپ می‌کند.

print_callers(*restrictions)

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

  • در profile، یک عدد در پرانتز پس از هر فراخواننده نمایش داده می‌شود تا نشان دهد این فراخوانی خاص چند بار انجام شده است. برای سهولت، یک عدد دوم بدون پرانتز، زمان تجمعی صرف‌شده در تابع سمت راست را تکرار می‌کند.

  • با cProfile، پیش از هر فراخواننده سه عدد قرار دارد: تعداد دفعاتی که این فراخوانی خاص انجام شده است، و زمان‌های کل و تجمعی صرف‌شده در تابع فعلی هنگامی که توسط این فراخواننده خاص فراخوانی شده است.

print_callees(*restrictions)

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

get_stats_profile()

این متد یک نمونه از StatsProfile را برمی‌گرداند، که شامل نگاشتی از نام‌های تابع به نمونه‌هایی از FunctionProfile است. هر نمونه از FunctionProfile اطلاعات مربوط به پروفایل تابع را نگهداری می‌کند، مانند مدت‌زمانی که اجرای تابع طول کشید، تعداد دفعاتی که فراخوانی شده است، و غیره...

اضافه شده در نسخه‌ی 3.9: کلاس‌های داده (dataclasses) زیر افزوده شدند: StatsProfile، FunctionProfile. تابع زیر افزوده شد: get_stats_profile.

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

پروفایل‌گیری قطعی (deterministic profiling) <Deterministic profiling> برای بازتاب این واقعیت در نظر گرفته شده است که تمام رویدادهای فراخوانی تابع، بازگشت تابع و استثنا پایش می‌شوند و زمان‌سنجی‌های دقیقی برای فاصله‌های زمانی بین این رویدادها (که در این فاصله، کد کاربر در حال اجراست) انجام می‌شود. در مقابل، پروفایل‌گیری آماری (statistical profiling) <statistical profiling> (که این ماژول آن را انجام نمی‌دهد) به‌صورت تصادفی از اشاره‌گر دستور مؤثر (effective instruction pointer) نمونه‌برداری می‌کند و استنتاج می‌کند که زمان کجا صرف می‌شود. روش دوم به‌طور سنتی سربار کمتری به همراه دارد (زیرا نیازی به ابزارمندسازی کد نیست)، اما فقط نشانه‌های نسبی درباره‌ی محل صرف زمان ارائه می‌دهد.

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

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

محدودیت‌ها

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

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

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

کالیبراسیون

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

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 یا cProfile.Profile استفاده می‌کنید، مقدار بازگشتی your_time_func به‌صورت متفاوتی تفسیر خواهد شد:

profile.Profile

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

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

cProfile.Profile

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

pr = cProfile.Profile(your_integer_time_func, 0.001)

از آن‌جا که کلاس cProfile.Profile قابل کالیبره نیست، توابع زمان‌سنج سفارشی باید با احتیاط استفاده شوند و تا حد ممکن سریع باشند. برای بهترین نتایج با یک زمان‌سنج سفارشی، ممکن است لازم باشد آن را در کد منبع C ماژول درونی _lsprof به‌صورت سخت‌کد (hard-code) پیاده‌سازی کنید.

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