~/icsd.ir — bash
SYSTEM_ONLINE

Performance و Profiling

قبل از بهینه‌سازی، باید بدانیم کجا کند است. در این فصل با ابزارهای پروفایل (timeit، cProfile، line_profiler، memory_profiler)، تکنیک‌های بهینه‌سازی، Big O، lazy evaluation و راه‌حل‌های نهایی (numpy، Cython) آشنا می‌شویم.

قبل از بهینه‌سازی، باید بدانیم کجا کند است. در این فصل با ابزارهای پروفایل (timeit، cProfile، line_profiler، memory_profiler)، تکنیک‌های بهینه‌سازی، Big O، lazy evaluation و راه‌حل‌های نهایی (numpy، Cython) آشنا می‌شویم.

قانون طلایی بهینه‌سازی

“Premature optimization is the root of all evil” – Donald Knuth

  1. اول کد درست بنویسید
  2. اگر کند بود، اندازه‌گیری کنید
  3. گلوگاه واقعی را پیدا و بهینه کنید
  4. دوباره اندازه‌گیری کنید

timeit – اندازه‌گیری ساده

import timeit

# اندازه‌گیری یک خط
t = timeit.timeit("'-'.join(str(n) for n in range(100))", number=10000)
print(f"{t:.4f}s")

# مقایسه دو روش
t1 = timeit.timeit(
    "'-'.join([str(n) for n in range(100)])",  # list comp
    number=10000
)
t2 = timeit.timeit(
    "'-'.join(str(n) for n in range(100))",     # generator
    number=10000
)
print(f"List: {t1:.4f}s")
print(f"Gen:  {t2:.4f}s")

# با setup
t = timeit.timeit(
    "f(100)",
    setup="from math import factorial as f",
    number=10000
)

در خط فرمان

python -m timeit "'-'.join(str(n) for n in range(100))"
# 10000 loops, best of 5: 30.5 usec per loop

python -m timeit -s "data = list(range(1000))" "sum(data)"

در Jupyter

# magic command
%timeit sum(range(1000))
%%timeit
total = 0
for i in range(1000):
    total += i

cProfile – پروفایل تابع‌محور

import cProfile

def slow_function():
    total = 0
    for i in range(1_000_000):
        total += i ** 2
    return total

# پروفایل
cProfile.run("slow_function()")

# ذخیره برای تحلیل
cProfile.run("slow_function()", "profile_output.prof")
# در خط فرمان
python -m cProfile -o output.prof script.py

# با مرتب‌سازی
python -m cProfile -s cumulative script.py

تحلیل با pstats

import pstats
from pstats import SortKey

p = pstats.Stats("profile_output.prof")
p.strip_dirs()                          # حذف مسیرهای طولانی
p.sort_stats(SortKey.CUMULATIVE)        # مرتب بر cumulative time
p.print_stats(20)                       # 20 ردیف اول

# یا
p.sort_stats(SortKey.TIME)              # زمان درون تابع
p.print_stats(20)

snakeviz – نمایش گرافیکی

pip install snakeviz
snakeviz profile_output.prof
# مرورگر باز می‌شود با نمودار interactive

line_profiler – پروفایل خط به خط

pip install line_profiler
# اضافه کردن @profile به تابع
@profile
def slow_function():
    total = 0
    for i in range(1_000_000):
        total += i ** 2
    return total

slow_function()
kernprof -lv script.py
# Line #  Hits  Time  Per Hit  % Time  Line
#      2      1     2      2.0      0.0  def slow_function():
#      3      1     1      1.0      0.0      total = 0
#      4 1000001  450000   0.5     45.0     for i in range(...):
#      5 1000000  550000   0.6     55.0         total += i ** 2

memory_profiler

pip install memory_profiler
from memory_profiler import profile

@profile
def memory_heavy():
    big_list = [i for i in range(10_000_000)]
    big_set = set(big_list)
    return len(big_set)

memory_heavy()
python -m memory_profiler script.py
# Line  Mem usage  Increment  Line Contents
#    3   38.4 MiB    0.0 MiB  @profile
#    4   38.4 MiB    0.0 MiB  def memory_heavy():
#    5  423.5 MiB  385.1 MiB      big_list = [...]
#    6  701.2 MiB  277.7 MiB      big_set = set(big_list)

تکنیک‌های بهینه‌سازی

۱. انتخاب درست ساختار داده

import time

# جستجو در list - O(n)
data_list = list(range(100_000))
start = time.perf_counter()
for _ in range(1000):
    99_999 in data_list
print(f"list: {time.perf_counter() - start:.4f}s")

# جستجو در set - O(1)
data_set = set(data_list)
start = time.perf_counter()
for _ in range(1000):
    99_999 in data_set
print(f"set:  {time.perf_counter() - start:.4f}s")

# تفاوت 10000 برابری!
عملیات list dict/set deque
دسترسی به ایندکس O(1) O(n)
جستجو in O(n) O(1) O(n)
append O(1) O(1)
insert(0) O(n) O(1)
pop(0) O(n) O(1)

۲. List Comprehension به‌جای حلقه

# کند
result = []
for i in range(1000):
    if i % 2 == 0:
        result.append(i ** 2)

# سریع‌تر (~30%)
result = [i ** 2 for i in range(1000) if i % 2 == 0]

# سریع‌تر برای aggregation
total = sum(i ** 2 for i in range(1000) if i % 2 == 0)

۳. تابع‌های built-in

# کند
total = 0
for x in numbers:
    total += x

# سریع - C implementation
total = sum(numbers)

# همچنین: max, min, any, all, sorted
# join به‌جای +=
result = "".join(parts)  # O(n)
# نه: result += part  # O(n^2)

۴. lru_cache برای توابع pure

from functools import lru_cache

@lru_cache(maxsize=None)
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

# fibonacci(100) را در میلی‌ثانیه محاسبه می‌کند

۵. متغیرهای محلی به‌جای global

import math

# کند - دسترسی به global در حلقه
def slow():
    result = []
    for i in range(1_000_000):
        result.append(math.sqrt(i))
    return result

# سریع‌تر - lookup محلی
def fast():
    sqrt = math.sqrt          # local reference
    result = []
    append = result.append    # local method reference
    for i in range(1_000_000):
        append(sqrt(i))
    return result

۶. __slots__

class Normal:
    def __init__(self, x, y):
        self.x = x
        self.y = y

class Slotted:
    __slots__ = ("x", "y")
    def __init__(self, x, y):
        self.x = x
        self.y = y

# برای 1 میلیون شیء:
# Normal:   ~64 MB
# Slotted:  ~32 MB (نصف)

یادآوری Big O

پیچیدگی مثال n=1M
O(1) dict lookup میلی‌ثانیه
O(log n) binary search میلی‌ثانیه
O(n) list iteration ثانیه
O(n log n) sort ثانیه
O(n²) nested loop ساعت
O(2^n) recursion بدون کش غیرممکن
# O(n²) - بد
duplicates = []
for i, x in enumerate(items):
    for j, y in enumerate(items):
        if i != j and x == y and x not in duplicates:
            duplicates.append(x)

# O(n) - خوب
from collections import Counter
counts = Counter(items)
duplicates = [k for k, v in counts.items() if v > 1]

NumPy – vectorization

import numpy as np
import time

# Python خالص
def slow_dot(a, b):
    return sum(x*y for x, y in zip(a, b))

# NumPy - vectorized
def fast_dot(a, b):
    return np.dot(a, b)

a = list(range(1_000_000))
b = list(range(1_000_000))

start = time.perf_counter()
slow_dot(a, b)
print(f"Python: {time.perf_counter() - start:.4f}s")  # ~0.15s

a_np = np.array(a)
b_np = np.array(b)
start = time.perf_counter()
fast_dot(a_np, b_np)
print(f"NumPy:  {time.perf_counter() - start:.4f}s")  # ~0.001s

# 100-500 برابر سریع‌تر!

پسوندهای C

ctypes – استفاده از کتابخانه C

import ctypes

# بارگذاری کتابخانه
libc = ctypes.CDLL("libc.so.6")  # Linux
# libc = ctypes.CDLL("msvcrt.dll")  # Windows

# فراخوانی printf
libc.printf(b"Hello from C!n")

Cython – کامپایل پایتون به C

pip install cython
# fast.pyx
def compute(int n):
    cdef int i, total = 0
    for i in range(n):
        total += i * i
    return total
# setup.py
from setuptools import setup
from Cython.Build import cythonize

setup(ext_modules=cythonize("fast.pyx"))

# بیلد: python setup.py build_ext --inplace
# استفاده:
import fast
fast.compute(1_000_000)  # ~30 برابر سریع‌تر از پایتون خالص

Async vs Threading vs Multiprocessing

import time

# I/O-bound - 100 درخواست HTTP
# Sync:           ~100s
# Threading(20):  ~5s
# Asyncio:        ~1s

# CPU-bound - محاسبه fibonacci
# Sync:                ~10s
# Threading(4):        ~10s (GIL!)
# Multiprocessing(4):  ~3s

پروفایل کردن Django

pip install django-debug-toolbar django-silk
# settings.py
INSTALLED_APPS = [
    ...
    "debug_toolbar",
    "silk",
]

MIDDLEWARE = [
    "debug_toolbar.middleware.DebugToolbarMiddleware",
    "silk.middleware.SilkyMiddleware",
    ...
]

# ابزار silk صفحه‌ای دارد که query‌های کند را نمایش می‌دهد

N+1 Query Problem

# بد - N+1 query
posts = Post.objects.all()
for post in posts:
    print(post.author.name)  # query جدا برای هر post!

# خوب - 1 query
posts = Post.objects.select_related("author").all()
for post in posts:
    print(post.author.name)  # بدون query اضافه

بهترین شیوه‌ها

  • قبل از بهینه‌سازی، اندازه‌گیری کنید
  • گلوگاه را پیدا کنید (معمولاً 20% کد، 80% زمان)
  • بعد از بهینه‌سازی، دوباره تست کنید
  • کاهش زمان: ابتدا الگوریتم، سپس ساختار داده، در آخر زبان
  • برای کارهای ریاضی، NumPy
  • برای N+1 در ORM، select_related/prefetch_related
  • کش (Redis، lru_cache) برای محاسبات تکراری

جمع‌بندی

  • timeit برای benchmark خط واحد
  • cProfile برای پروفایل کل برنامه
  • line_profiler برای دیدن خط به خط
  • memory_profiler برای حافظه
  • تکنیک‌ها: built-ins، set/dict، lru_cache، list comp
  • NumPy برای محاسبات عددی
  • Cython/C extensions برای حداکثر سرعت

نمایش سایت

رنگ سایت
حالت نمایش
اندازهٔ متن
خوانایی

این تنظیمات فقط روی مرورگر شما ذخیره می‌شود.