σμ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
  • 日本語
  • 中文
σμ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
  • 日本語
  • 中文
مسار Cache locality›الوحدة 3 · الجهاز بصورة أوسع›الموضوع 3.3·~55 دمتقدم

المشاركة الزائفة والحشو

الموضع في المسارالوحدة 3 من 3 · الموضوع 3 من 7
3.3 المشاركة الزائفة والحشو
—درجة المختبر— / 100إجمالي XP—التتابع—

4 دقيقة قراءة3 أمثلة برمجية

  1. 01للأنوية ذاكرات مخبئية خاصة بها
  2. 02عدّاد لكل نواة
  3. 03كم من الحشو
  4. 04عدّادات تقودها تواريخ حقيقية
  5. 05ما ستبنيه

تحدّث أربعة خيوط كل منها عدّادها الخاص ولا يُشارَك شيء ومع ذلك تعمل الحلقة أبطأ 40 مرة مما يجب. السبب أن العدّادات الأربعة في سطر ذاكرة مخبئية واحد وتتنازعه الأنوية. يبني هذا الدرس نموذجًا صغيرًا لتماسك الذاكرة المخبئية ويبيّن كلفة المشاركة الزائفة في حلقة تقودها تواريخ جدول SPY ويصلحها ببضعة بايتات من الحشو.

01

للأنوية ذاكرات مخبئية خاصة بها

حتى الآن كان برنامج يعمل على نواة واحدة. للجهاز الحقيقي عدة أنوية ولكل نواة ذاكرة مخبئية صغيرة سريعة خاصة بها. وهذا يخلق مشكلة: إذا حملت النواة 0 والنواة 1 نسخة من سطر وغيّرته النواة 0 فلا يجوز للنواة 1 أن تواصل قراءة القيمة القديمة. القاعدة العتادية التي تمنع ذلك هي تماسك الذاكرة المخبئية. أبسط طريقة لفرضها: قبل أن تكتب نواة في سطر تُرمى نسخة ذلك السطر في كل الأنوية الأخرى. وهذا إبطال.

  1. 1
    انظر في ذاكرتك المخبئيةإن لم يكن السطر هناك فهذا الوصول إخفاق.
  2. 2
    عند الكتابة اطرد الآخرينكل نواة أخرى تحمل السطر تفقد نسختها: إبطال واحد لكل منها.
  3. 3
    احتفظ بالسطربعد ذلك يكون السطر في ذاكرة هذه النواة جاهزًا لوصولها التالي.

يحتفظ الصنف أدناه لكل نواة بمجموعة الأسطر التي تحملها. نعيد ثلاث خطوات: تكتب النواة 0 البايت 0 ثم تكتب النواة 1 البايت 8 (السطر نفسه) ثم تكتب النواة 0 البايت 0 مرة أخرى.

main.pyPython
class CoherentCaches:    def __init__(self, n_cores):        self.lines = [set() for _ in range(n_cores)]        self.misses = 0        self.invalidations = 0    def access(self, core, address, write):        line = address // LINE_BYTES        hit = line in self.lines[core]        if not hit:            self.misses += 1        if write:            for other, held in enumerate(self.lines):1                if other != core and line in held:                    held.discard(line)                    self.invalidations += 1        self.lines[core].add(line)        return hitcaches = CoherentCaches(2)print(caches.access(0, 0, True), caches.access(1, 8, True), caches.access(0, 0, True))print("misses:", caches.misses, "invalidations:", caches.invalidations)
False False False
misses: 3 invalidations: 2
  1. 1الكتابة تزيل السطر من كل نواة أخرى تحمله وتعدّ إبطالًا واحدًا لكل نسخة.

تخفق الوصولات الثلاثة. كتابة النواة 1 تبطل نسخة النواة 0 والكتابة التالية للنواة 0 تبطل بدورها نسخة النواة 1. لم تكتب النواتان 0 و1 البايت نفسه أبدًا بل بايتين متجاورين ومع ذلك تنتزع كل منهما السطر من الأخرى عند كل كتابة.

تعريفالمشاركة الزائفة

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

02

عدّاد لكل نواة

طريقة شائعة لعدّ الأحداث على التوازي هي إعطاء كل نواة عدّادها الخاص حتى لا تنتظر نواة أخرى. حجم كل عدّاد 8 بايت. ضعها واحدًا بعد آخر في مصفوفة فيتسع أربعة منها في سطر من 64 بايت. نبني كتابات 50 جولة تحديث بكتابة لكل نواة في كل جولة ونعيد تشغيلها والعدّادات متباعدة 8 بايت (متراصة) و64 بايت (محشوة). الزمن 1 ns لوصول في ذاكرة النواة نفسها و60 ns لإخفاق يجلب السطر من نواة أخرى.

main.pyPython
def counter_address(core, stride_bytes):    return core * stride_bytesdef update_trace(n_cores, n_updates, stride_bytes):    return [(core, "w", counter_address(core, stride_bytes)) for _ in range(n_updates) for core in range(n_cores)]def run_trace(trace, n_cores):    caches = CoherentCaches(n_cores)    for core, op, address in trace:        caches.access(core, address, op == "w")    return {"accesses": len(trace), "misses": caches.misses, "invalidations": caches.invalidations}def time_ns(stats):    return (stats["accesses"] - stats["misses"]) * HIT_NS + stats["misses"] * MISS_NSfor name, stride in (("packed", 8), ("padded", 64)):    stats = run_trace(update_trace(4, 50, stride), 4)    print(name, stats, time_ns(stats), "ns")
packed {'accesses': 200, 'misses': 200, 'invalidations': 199} 12000.0 ns
padded {'accesses': 200, 'misses': 4, 'invalidations': 0} 436.0 ns

عند التراص تخفق الكتابات الـ200 كلها و199 منها تبطل الكاتب السابق: 12000 ns. وعند الحشو تحتفظ كل نواة بعدّادها في سطر خاص بها: أربعة إخفاقات باردة في البداية ثم تصيب كل كتابة ولا إبطال: 436 ns. هذا أسرع بـ27 مرة لنفس الحساب تمامًا لأن الأنوية الأربع لم تعد تنتزع السطر من بعضها.

03

كم من الحشو

لإعطاء كل عدّاد سطرًا خاصًا يجب أن تكون المسافة بين عدّادين مضاعفًا لحجم السطر فيُقرَّب حجم العنصر إلى المضاعف التالي للعدد 64. عدّاد من 8 بايت يأخذ 64 بايت أي يهدر 56. وعنصر من 65 بايت يأخذ 128. تقوم الدالة أدناه بالتقريب.

main.pyPython
def padding_needed(item_bytes):    return math.ceil(item_bytes / LINE_BYTES) * LINE_BYTESprint([padding_needed(size) for size in (8, 63, 64, 65, 100)])
[64, 64, 64, 128, 128]
كيف تطلبه في شيفرة حقيقية

في C++ اكتب alignas(64) على البنية التي تحمل عدّاد خيط (أو std::hardware_destructive_interference_size) وفي Rust #[repr(align(64))] وفي Java @Contended. وفي Python مع NumPy اجعل مصفوفة العدّادات (n_cores, 8) واستعمل العمود 0 فقط ليبدأ كل عدّاد صفًّا جديدًا بحجم 64 بايت.

احشُ فقط ما تكتبه الأنوية

القراءات وحدها لا تُبطل شيئًا فتستطيع أنوية كثيرة قراءة سطر بلا كلفة. ويهدر الحشو 56 بايت لكل عدّاد من 8 بايت وهي صفقة سيئة لبيانات تُقرأ فقط أو لآلاف العناصر. احتفظ بالحشو للمتغيرات الساخنة القليلة الخاصة بكل خيط التي تحدّثها الخيوط كثيرًا.

04

عدّادات تقودها تواريخ حقيقية

في المختبر تعدّ أربع أنوية كل منها أيام التداول التي تتولاها ويذهب اليوم رقم i من جدول SPY إلى النواة يوم الأسبوع % 4. تقود أول 1000 يوم الكتابات بترتيب التاريخ. ولأن الأيام تتبع التقويم نادرًا ما تكتب النواة نفسها مرتين متتاليتين فيُنتزع السطر مرة بعد مرة. مع العدّادات المتراصة تخفق 818 كتابة من 1000 وتبطل 817 منها نواة أخرى؛ ومع المحشوّة تخفق الكتابات الأربع الأولى فقط.

ما تقوله البيانات الحقيقية1000 يوم تداول لـ SPY، 4 أنوية، 1 ns للإصابة و60 ns للإخفاق
عدّادات متراصة في سطر واحد49,262ns818 إخفاقًا و817 إبطالًا
عدّادات محشوة إلى 64 بايت1,236ns4 إخفاقات باردة ولا إبطال
التسارع بالحشو39.9×العمل نفسه وسطر لكل عدّاد
الذاكرة المدفوعة مقابله56bytesمهدرة لكل عدّاد من 8 بايت
زمن 1000 تحديث بالميكروثانية
متراصة49.3
محشوة1.2
05

ما ستبنيه

يبني المختبر نموذج التماسك وتتبّعات العدّادات لكل نواة بخطوة معطاة وإعادة التشغيل وقاعدة الحشو ونموذج الزمن والدراسة على تواريخ SPY.

  • الدالة · السؤالتعتمد على
  • CoherentCaches(n_cores).access(core, address, write)إصابة أم إخفاق ومن يفقد نسخة؟حساب بسيط
  • counter_address(core, stride_bytes)أين يوجد عدّاد نواة؟حساب بسيط
  • update_trace(n_cores, n_updates, stride_bytes)ما الكتابات التي تجريها الأنوية؟counter_address
  • run_trace(trace, n_cores)كم إخفاقًا وإبطالًا؟CoherentCaches
  • padding_needed(item_bytes)كم يجب أن تتباعد العناصر؟حساب بسيط
  • time_ns(stats)كم تستغرق عملية التشغيل؟كلفتا الإصابة والإخفاق
  • study(bars)كم تكلّف المشاركة الزائفة على تواريخ SPY؟run_tracepadding_neededtime_ns

أهم الخلاصات

  • الكتابة تأخذ السطر. لتكتب النواة تبطل نسخة السطر في كل الأنوية الأخرى فتخفق في المرة التالية التي تستعمله فيها.
  • الجيران يتشاركون المصير. المتغيرات في سطر واحد من 64 بايت وحدة واحدة بالنسبة للأنوية: المشاركة الزائفة تجعل الشيفرة المتوازية أبطأ من التسلسلية.
  • احشُ البيانات الساخنة لكل نواة. قرّب كل عنصر إلى 64 بايت: أسرع 40 مرة هنا مقابل 56 بايت مهدرة لكل عدّاد؛ ولا تحشُ بيانات تُقرأ فقط.
تحقق سريع
يزيد خيطان كل منهما عدّاده الخاص من 8 بايت والعدّادان متجاوران في مصفوفة. ماذا يحدث؟
اختر إجابة.

الموضوع التالي: تتعلق المشاركة الزائفة بأنوية تتشارك مقبسًا. في جهاز بمقبسين تنقسم الذاكرة نفسها: جزء قريب من نواة وجزء بعيد عنها. في تأثيرات NUMA تضع الصفحات على عقد وتعدّ الوصولات المحلية والبعيدة وتقدّر الفرق.

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

7 مهام · 100 نقطة
  • نموذج تماسك30 نقطة
  • عنوان عدّاد5 نقطة
  • جولات تحديث15 نقطة
  • إعادة تشغيل15 نقطة
  • الحشو الذي يعطي سطرًا10 نقطة
  • زمن تشغيل10 نقطة
  • الدراسة على جدول SPY15 نقطة
المستكشف · lab-cl-9
▾ lab-cl-9
▾ data
…
bash · داخل المتصفح
learner@quantcraft:~/lab-cl-9$
لم يتم تحميل Python بعدPythonLn 1, Col 1Spaces: 4UTF-8

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

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

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