σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
uantCraft Academyالمسارات
Ctrl K
AO
+1.2k متعلّم
تسجيل الدخولانضم ↗
المسارات
AO
+1.2k متعلّم
تسجيل الدخول
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
مسار Python›الوحدة 10 · هندسة Python عالية الأداء›الموضوع 10.4·~50 دمتقدم

تحليل أداء شيفرة Python

الموضع في المسارالوحدة 10 من 11 · الموضوع 4 من 13
10.4 تحليل أداء شيفرة Python
—درجة المختبر— / 100إجمالي XP—التتابع—

قِس أولًا ثم حسّن

اختبار رجعي يستغرق أربع ساعات أو دالة تسعير تُستدعى مليون مرة في الثانية تجعل الجميع يريد التسريع، ويخطئ معظمهم في تخمين أين يذهب الوقت. فقد تكون الحلقة التي تبدو ثقيلة لا شيء، وقد يكون سطر بريء المظهر هو التشغيل كله. وتحليل الأداء (profiling) هو قياس أين ينفق البرنامج وقته قبل تغيير أي شيء. والقاعدة بسيطة: لا تحسّن سطرًا لم تقِسه، وبعد التغيير قِس من جديد لترى هل أفاد.

# The simplest measurement: read a clock before and after
import time

def slow_sum(n):
    total = 0
    for i in range(n):
        total += i * i
    return total

start = time.perf_counter()
result = slow_sum(200_000)
elapsed = time.perf_counter() - start
print(result)                                        # 2666646666700000
print(elapsed > 0)                                   # True  a real duration; the exact value changes every run

الضجيج والتكرار والحد الأدنى

القياس الواحد غير موثوق: نظام التشغيل وبرامج أخرى وذاكرة المعالج المؤقتة وبدء بايثون كلها تضيف ضجيجًا ولا تضيف إلا وقتًا. لذا قِس الدالة عدة مرات وخذ الحد الأدنى، أي التشغيل الأقل تشويشًا؛ وهذا ما تفعله timeit. واستعمل time.perf_counter() (ساعة رتيبة عالية الدقة) لا time.time(). وقِس بأحجام مدخلات واقعية، فقد تكون دالة سريعة على عشرة صفوف بطيئة على عشرة ملايين، وسخّن أولًا حتى لا تُحتسب التكاليف لمرة واحدة (الاستيراد، الذاكرة المؤقتة) على الشيفرة المقيسة.

# Repeat and take the minimum: the least disturbed run
import time

def work():
    return sum(i * i for i in range(50_000))

def best_of(func, repeats):
    best = None
    for _ in range(repeats):
        start = time.perf_counter()
        func()
        elapsed = time.perf_counter() - start
        if best is None or elapsed < best:
            best = elapsed
    return best

runs = [best_of(work, 1) for _ in range(5)]
print(best_of(work, 5) <= max(runs))                 # True  the minimum is never above the worst single run
print(min(runs) > 0)                                 # True

أين يذهب الوقت: المحلّلات وقانون أمدال

يخبرك المؤقّت كم، ويخبرك المحلّل (profiler) أين. تسجّل cProfile في المكتبة القياسية كل نداء دالة وتبلّغ لكل دالة عن عدد النداءات والزمن المنفق في الدالة نفسها والزمن التراكمي بما نادته؛ وpython -m cProfile -s cumtime script.py يطبع الأسوأ أولًا. وحصة كل دالة هي ببساطة زمنها على الإجمالي. ومكسب إصلاح دالة تحدّه قاعدة أمدال: تسريع جزء f من التشغيل بمعامل s يعطي إجمالًا 1 / ((1 - f) + f / s). فجعل 60% من التشغيل أسرع عشر مرات يعطي 2.2 مرة فقط، وحتى نصف سريع بلا حدود يحد المكسب بـ 2. ابدأ بأكبر نقطة ساخنة.

# Shares of the total and the payoff of fixing the biggest one (the timings are made-up numbers)
timings = {"load_prices": 6.0, "price_options": 3.0, "write_report": 1.0}
total = sum(timings.values())
for name, seconds in sorted(timings.items(), key=lambda item: -item[1]):
    print(f"{name:14s} {seconds / total:5.1%}")      # load_prices 60.0%, price_options 30.0%, write_report 10.0%

def amdahl_gain(fraction, speedup):
    return 1 / ((1 - fraction) + fraction / speedup)

print(round(amdahl_gain(0.6, 10), 2))                # 2.17  ten times faster on 60% of the run
print(round(amdahl_gain(0.1, 1000), 2))              # 1.11  a huge win on the smallest part barely helps
# cProfile records every call; the function that sleeps shows up as the hotspot
import cProfile
import time

def slow_part():
    time.sleep(0.02)

def fast_part():
    return sum(range(10))

def run_all():
    slow_part()
    for _ in range(100):
        fast_part()

profiler = cProfile.Profile()
profiler.runcall(run_all)
entries = {str(entry.code): entry for entry in profiler.getstats()}
slow = next(entry for name, entry in entries.items() if "slow_part" in name)
fast = next(entry for name, entry in entries.items() if "fast_part" in name)
print(fast.callcount, slow.callcount)                # 100 1   calls are counted exactly
print(slow.totaltime > fast.totaltime)               # True    one slow call outweighs 100 fast ones
حلّل الأداء في ظروف مهمةيبطئ المحلّل البرنامج الذي يراقبه ويشوّه الدوال الصغيرة جدًا، فثق بالصورة الكبرى (أي دالة تهيمن) لا بالثواني الدقيقة، وأكّد التحسن بمؤقّت بسيط. وحلّل بأحجام بيانات حقيقية لا لعبة. وتحقق من بقاء النتيجة صحيحة بعد كل تغيير فإجابة خاطئة أسرع هي أغلى أنواع الأخطاء. وتوقف حين تصير الشيفرة سريعة بما يكفي: فالوقت المنفق في صقل نقطة ساخنة لم تعد مهمة وقت لم يُنفق على الاستراتيجية.
  • قِس قبل تغيير أي شيء؛ استعمل perf_counter وكرّر واحتفظ بالحد الأدنى بأحجام مدخلات واقعية.
  • يُظهر المحلّل (cProfile) أين يذهب الوقت: النداءات والزمن الذاتي والتراكمي لكل دالة؛ وحصة كل منها زمنها على الإجمالي.
  • قاعدة أمدال: تسريع جزء f بمعامل s يعطي 1 / ((1 - f) + f / s)؛ أصلح أكبر نقطة ساخنة أولًا وأعد القياس بعد كل تغيير.

التالي: تحسين الحلقات الساخنة، التغييرات التي تسرّع الشيفرة التي أشار إليها المحلّل.

المختبر: ما الذي يُقيَّم

  • قِس زمن نداء واحد باستخدام time_call25 نقطة
  • خذ أفضل تشغيل من عدة تشغيلات باستخدام best_of25 نقطة
  • اعثر على أين يذهب الوقت باستخدام hotspots25 نقطة
  • توقّع المكسب باستخدام amdahl_gain25 نقطة
المستكشف · lab-120-1
▾ lab-120-1
…
bash · داخل المتصفح
learner@quantcraft:~/lab-120-1$
لم يتم تحميل Python بعدPythonLn 1, Col 1Spaces: 4UTF-8

هل تريد إعادة ضبط هذا الموضوع؟

سيعود كل ما في lab-120-1 إلى الملفات الابتدائية وستُمسح الطرفية. ستفقد حلّك الحالي ولا يمكن التراجع عن ذلك.

سيُعاد ضبطه
  • main.py ← الشيفرة الابتدائية
  • سجل الطرفية ومخرجات الاختبارات
  • التغييرات غير المُرسلة
سيُحتفظ به
  • أفضل درجة في المختبر
  • نقاط XP والتتابع
  • تقدّم المحاضرة والملاحظات

سجّل الدخول لتشغيل الكود الخاص بك.

قراءة كل المحاضرات مجانية. أنشئ حسابًا أو سجّل الدخول لتشغيل الكود وتنفيذ الاختبارات وتسليم هذا المختبر.

  • بيئة تطوير داخل المتصفح مع طرفية bash — لا حاجة لتثبيت أي شيء
  • تصحيح فوري ونقاط خبرة ومكانك في المسار
إنشاء حسابتسجيل الدخول
أو
المتابعة عبر GitHub