FA-TOOLS — Header Component
آموزش profiling کد پایتون — پیدا کردن کند‌ترین بخش

آموزش profiling کد پایتون — پیدا کردن کند‌ترین بخش

آنچه در این مقاله یاد می‌گیرید:

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

اگر برنامه پایتون شما کند اجرا می‌شود، حدس زدن دلیل آن کافی نیست و معمولاً به بهینه‌سازی بخش‌های اشتباه منجر می‌شود. پروفایلینگ (Profiling) به شما نشان می‌دهد دقیقاً کدام خطوط و توابع بیشترین زمان یا حافظه را مصرف می‌کنند تا بدون هدر دادن وقت، کدهای خود را بهینه‌سازی کنید. با این روش، شما مستقیماً روی ۲۰ درصدی از کد تمرکز می‌کنید که ۸۰ درصد زمان اجرا را به خود اختصاص داده است.

۱. تفاوت اندازه‌گیری زمان ساده (Timing) با پروفایلینگ

آموزش profiling کد پایتون — پیدا کردن کند‌ترین بخش — تصویر 1

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

بسیاری از برنامه‌نویسان برای سنجش سرعت کدهای خود از کتابخانه time استفاده می‌کنند؛ به این صورت که زمان قبل و بعد از اجرای یک تابع را ذخیره کرده و تفاضل آن‌ها را محاسبه می‌کنند. اگرچه این روش برای سنجش‌های کوچک مفید است، اما دو ایراد بزرگ دارد:

  • به شما نمی‌گوید که درون آن تابع، دقیقاً کدام حلقه، خط یا فراخوانی کتابخانه خارجی باعث کندی شده است.
  • اضافه کردن و پاک کردن کدهای اندازه‌گیری زمان در بخش‌های مختلف پروژه، فرآیندی خسته‌کننده و مستعد خطا است.

۲. مقایسه ابزارهای محبوب پروفایلینگ در پایتون

آموزش profiling کد پایتون — پیدا کردن کند‌ترین بخش — تصویر 2

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

نام ابزار (Tool) کاربرد اصلی و ویژگی بارز
cProfile ابزار داخلی پایتون برای سنجش زمان اجرای توابع با کمترین اثر منفی روی سرعت اجرا (Overhead کم).
line_profiler آنالیز خط به خط زمان اجرای یک تابع خاص؛ عالی برای یافتن حلقه‌ها یا خطوط کند.
memory_profiler اندازه‌گیری و مانیتورینگ مصرف حافظه (RAM) خط به خط کد برای پیدا کردن نشتی حافظه.
SnakeViz یک ابزار وب‌بیس برای تصویرسازی و رسم نمودارهای درختی از خروجی‌های cProfile.

۳. آموزش گام‌به‌گام کار با cProfile

آموزش profiling کد پایتون — پیدا کردن کند‌ترین بخش — تصویر 3

ماژول cProfile چیست؟ cProfile استانداردترین و سریع‌ترین ابزار داخلی پایتون برای سنجش عملکرد است که تعداد دفعات فراخوانی و زمان صرف شده در هر تابع را ثبت می‌کند. این ابزار به صورت پیش‌فرض در پایتون نصب است و نیازی به نصب پکیج اضافی ندارد.

روش اول: اجرای cProfile از طریق خط فرمان (CLI)

ساده‌ترین راه برای آنالیز کل یک فایل پایتون بدون دستکاری کدهای آن، اجرای دستور زیر در ترمینال است:

python -m cProfile -s tottime my_script.py

توضیح سوییچ‌های دستور بالا:

  • -m cProfile: فعال‌سازی ماژول پروفایلر پایتون.
  • -s tottime: مرتب‌سازی خروجی بر اساس زمان کلی صرف‌شده در هر تابع (کلیدی‌ترین فاکتور برای یافتن توابع کند).

روش دوم: استفاده از cProfile درون کد پایتون

اگر می‌خواهید فقط بخش خاصی از پروژه بزرگ خود را آنالیز کنید، می‌توانید کلاس Profile را مستقیماً در کدهایتان ایمپورت کنید:

import cProfile
import pstats

def heavy_calculation():
    # یک عملیات فرضی سنگین
    return sum(i * i for i in range(10000000))

# شروع پروفایلینگ
profiler = cProfile.Profile()
profiler.enable()

# اجرای تابع هدف
heavy_calculation()

# پایان پروفایلینگ
profiler.disable()

# چاپ نتایج به صورت مرتب‌شده
stats = pstats.Stats(profiler).sort_stats('cumtime')
stats.print_stats(10) # فقط نمایش ۱۰ نتیجه اول

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

تحلیل ستون‌های خروجی cProfile

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

  • ncalls: تعداد دفعاتی که تابع صدا زده شده است.
  • tottime: کل زمان صرف شده در این تابع (بدون احتساب زمان توابع فرعی که از داخل این تابع صدا زده شده‌اند).
  • percall: زمان متوسط برای هر بار اجرای تابع (حاصل تقسیم tottime بر ncalls).
  • cumtime: کل زمان تجمعی صرف شده در این تابع و تمام زیرتوابع آن از ابتدا تا انتها.
  • filename:lineno(function): نام فایل، شماره خط و نام دقیق تابع مربوطه.

۴. بررسی خط‌به‌خط کد با line_profiler

line_profiler چیست؟ ابزاری قدرتمند در پایتون است که زمان دقیق صرف شده روی تک‌تک خطوط یک تابع خاص را اندازه‌گیری می‌کند تا متوجه شوید دقیقاً کدام خط در یک تابع طولانی مسبب اصلی کندی است.

برای نصب این پکیج، دستور زیر را در ترمینال خود اجرا کنید:

pip install line-profiler

نحوه استفاده از line_profiler

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

# heavy_test.py

@profile
def my_function():
    a = [i for i in range(1000000)]
    b = [j * 2 for j in a]
    return b

if __name__ == "__main__":
    my_function()

حالا برای اجرای این اسکریپت با مانیتورینگ خط به خط، دستور kernprof را در خط فرمان وارد کنید:

kernprof -l -v heavy_test.py

سوییچ -l نشان‌دهنده بررسی خط‌به‌خط (line-by-line) و سوییچ -v باعث نمایش سریع خروجی در ترمینال بعد از اتمام کار برنامه می‌شود. خروجی حاوی درصدی از زمان است که هر خط به خود اختصاص داده است.

۵. آنالیز مصرف حافظه با memory_profiler

کند بودن یک اسکریپت پایتون همیشه به خاطر پردازش سنگین CPU نیست؛ گاهی اوقات پر شدن حافظه موقت (RAM) و لود کردن حجم عظیمی از داده‌ها در حافظه، مسبب افت شدید پرفورمنس کل سیستم می‌شود.

کتابخانه memory_profiler به شما اجازه می‌دهد متوجه شوید هر خط از کد چه مقدار حافظه به مگابایت مصرف یا آزاد می‌کند.

ابتدا کتابخانه مورد نیاز را نصب کنید:

pip install memory_profiler psutil
نکته مهم: نصب ماژول psutil اختیاری است، اما اکیداً توصیه می‌شود؛ زیرا سرعت اجرای فرآیند سنجش حافظه را چند برابر می‌کند.

دقیقاً مانند ابزار قبلی، باید از دکوراتور @profile استفاده کنید، اما این‌بار برنامه را با ماژول حافظه اجرا می‌کنیم:

# memory_test.py
from memory_profiler import profile

@profile
def allocate_mem():
    large_list = [i for i in range(1000000)]
    del large_list
    return True

if __name__ == "__main__":
    allocate_mem()

سپس اسکریپت را به طور مستقیم اجرا کنید تا خروجی گام‌به‌گام مصرف رم را ببینید:

python memory_test.py

۶. تصویرسازی نتایج با SnakeViz

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

برای شروع ابتدا آن را نصب کنید:

pip install snakeviz

ابتدا باید اطلاعات خروجی پروفایلر را در یک فایل با پسوند .prof ذخیره کنید:

python -m cProfile -o program_stats.prof my_script.py

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

snakeviz program_stats.prof

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

۷. راهنمای عیب‌یابی سریع مشکلات رایج

هنگام کار با ابزارهای پروفایلینگ ممکن است به مشکلاتی برخورد کنید. در این بخش راه‌حل‌های سریعِ چالش‌های متداول را بررسی می‌کنیم:

خطای NameError: name ‘profile’ is not defined در زمان استفاده از line_profiler

علت: این خطا زمانی رخ می‌دهد که اسکریپت را به جای دستور kernprof با دستور عادی python اجرا کرده باشید.
راه‌حل: یا کد خود را حتماً با دستور kernprof -l -v file.py اجرا کنید یا در ابتدای کد پایتون شرط زیر را اضافه کنید تا خطا رخ ندهد:

if 'profile' not in __builtins__:
    __builtins__.profile = lambda f: f
کند شدن شدید کد زمان استفاده از memory_profiler

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

خروجی cProfile خیلی شلوغ و غیرقابل فهم است

علت: پایتون تمام فراخوانی‌های کتابخانه‌های سیستمی و توابع زیرمجموعه را لاگ می‌کند.
راه‌حل: از فیلتر کردن خروجی در کلاس pstats استفاده کنید و یا آن را به صورت مستقیم در SnakeViz باز کنید تا صرفاً نمودار درختی بخش‌های مهم به شما نمایش داده شود.

۸. پرسش‌های متداول

آیا استفاده از cProfile سرعت اجرای برنامه را به شدت کاهش می‌دهد؟

خیر، cProfile به زبان C نوشته شده است و سربار (Overhead) بسیار ناچیزی دارد. به همین دلیل استفاده از آن بر روی کدهای طولانی تفاوت زمانی آزاردهنده‌ای ایجاد نمی‌کند.

چگونه بفهمیم یک قطعه کد دچار نشت حافظه (Memory Leak) شده است؟

با استفاده از ابزار memory_profiler اگر متوجه شوید پس از پایان اجرای یک تابع و حذف متغیرها، مقدار مصرفی حافظه رم آزاد نمی‌شود و روند صعودی دارد، کد شما دچار نشت حافظه شده است.

آیا امکان پروفایل کردن کدهای چندنخی (Multithreading) وجود دارد؟

به طور پیش‌فرض cProfile فقط ترد اصلی (Main Thread) را پروفایل می‌کند. برای برنامه‌های چندنخی باید پروفایلر را به شکل دستی درون هر ترد اجرا کنید و یا از ابزارهای نمونه‌بردار مانند Py-Spy استفاده کنید.

تفاوت بین deterministic profilers و sampling profilers چیست؟

ابزارهای Deterministic مانند cProfile تمام رویدادها و فراخوانی‌ها را دقیق ثبت می‌کنند اما کمی سربار دارند. ابزارهای Sampling مانند Py-Spy در فواصل زمانی مشخص از برنامه نمونه‌برداری می‌کنند که سرعت آن صفر درصد کاهش می‌یابد اما دقت ۱۰۰ درصد جزئیات را ندارد.

Table of Contents

آخرین نوشته‌ها