قبل أن تصل قراءة إلى الذاكرة المخبئية تحتاج إلى عنوان فعلي وللترجمة ذاكرة مخبئية صغيرة خاصة بها هي TLB فيها نحو 64 مدخلًا فقط. قد يخفق في TLB في كل قراءة تقريبًا برنامج يقفز بين صفحات أكثر من ذلك حتى لو كانت ذاكرات بياناته المخبئية جيدة. يبني هذا الدرس TLB ويبيّن كيف تحوّل صفحة 2 ميغابايت 250 صفحة إلى صفحة واحدة.
نوعان من العناوين
العناوين التي يستعملها البرنامج افتراضية: يظن كل برنامج أنه يملك صفًّا طويلًا من الذاكرة يبدأ من الصفر. وتحتفظ الآلة بالذاكرة الحقيقية بعناوين فعلية وتقطّع العالمين إلى صفحات. جدول في الذاكرة هو جدول الصفحات يقول أي صفحة فعلية تحوي كل صفحة افتراضية. تحتاج كل قراءة أولًا إلى هذه الترجمة: خذ رقم الصفحة من العنوان الافتراضي وابحث عنه واحتفظ بموضع البايت داخل الصفحة. إنه التقسيم نفسه لسطر الذاكرة المخبئية بكتلة أكبر.
page_bytes = 4096for address in (0, 4095, 4096, 123456789): print(address, "-> page", address // page_bytes, "byte", address % page_bytes)1
0 -> page 0 byte 0 4095 -> page 0 byte 4095 4096 -> page 1 byte 0 123456789 -> page 30140 byte 3349
- 1رقم الصفحة هو العنوان مقسومًا على حجم الصفحة والباقي هو البايت داخل الصفحة.
ذاكرة مخبئية للترجمات
قد يضاعف تصفّح جدول الصفحات في الذاكرة عند كل قراءة كلفة الوصول إلى الذاكرة، لذا يحتفظ المعالج بآخر ترجماته في جدول صغير سريع اسمه TLB. قراءة صفحتها في TLB هي إصابة ولا تكلّف شيئًا تقريبًا. وقراءة لا توجد صفحتها فيه هي إخفاق TLB ويطلق تصفّحًا لجدول الصفحات يستغرق عشرات النانوثوانٍ. يحوي TLB للبيانات من المستوى الأول نحو 64 مدخلًا. وعدا أنه يحفظ صفحات بدل أسطر يعمل مثل الذاكرات المخبئية السابقة: يخرج أولًا الأقل استعمالًا مؤخرًا.
class TLB: def __init__(self, entries): self.entries = entries self.pages = [] self.hits = 0 self.misses = 0 def access(self, address, page_bytes): page = address // page_bytes if page in self.pages: self.pages.remove(page) self.pages.append(page) self.hits += 1 return True self.misses += 1 self.pages.append(page) if len(self.pages) > self.entries: self.pages.pop(0) return Falsedef tlb_misses(trace, entries, page_bytes): tlb = TLB(entries) for address in trace: tlb.access(address, page_bytes) return tlb.missesfor n_pages in (48, 64, 65, 80): trace = [page * 4096 for page in range(n_pages)] * 5 print(n_pages, "pages, 5 rounds:", tlb_misses(trace, 64, 4096), "misses")
48 pages, 5 rounds: 48 misses 64 pages, 5 rounds: 64 misses 65 pages, 5 rounds: 325 misses 80 pages, 5 rounds: 400 misses
حلقة على 48 أو 64 صفحة لا تخفق إلا في الجولة الأولى. صفحة واحدة زائدة أي 65 فيخفق TLB في كل قراءة من الجولات الخمس: 325 إخفاقًا من 325. هذه هي الهاوية نفسها لسياسة LRU في موضوع مجموعات العمل لكن بالصفحات. المهم هو المدى: كم ذاكرة يغطي TLB دفعة واحدة.
قراءة سجلات عشوائيًا
خذ جدولًا من 2000 سجل حجم كل منها 512 بايت: بيانات يوم مع حقول إضافية. يشغل 1000 كيلوبايت أي 250 صفحة من 4 كيلوبايت وهو نحو أربعة أضعاف الصفحات الـ64 التي يتسع لها TLB. قراءة السجلات بالترتيب لطيفة: يتشارك ثمانية سجلات كل صفحة فيخفق TLB مرة لكل صفحة أي 250 مرة. وقراءتها بترتيب عشوائي صعبة: كل قراءة قد تقع على أي من 250 صفحة ويحتفظ TLB بـ64 منها فقط. يقيس الكود أدناه الحالتين بصفحات 4 كيلوبايت وبصفحات 2 ميغابايت.
import randomorder = list(range(2000))shuffled = random.Random(7).sample(order, len(order))for name, records in (("in order", order), ("shuffled", shuffled)): trace = [record * 512 for record in records] print(name, "4 KB pages:", tlb_misses(trace, 64, 4096), "- 2 MB pages:", tlb_misses(trace, 64, 2 * 1024 * 1024))
in order 4 KB pages: 250 - 2 MB pages: 1 shuffled 4 KB pages: 1541 - 2 MB pages: 1
بالترتيب تكلّف الصفحات 4 كيلوبايت 250 إخفاقًا. وعند خلطها تكلّف 1541 إخفاقًا من 2000 قراءة أي 77% من القراءات وبستة أضعاف. وبصفحات 2 ميغابايت تتسع الـ1000 كيلوبايت كلها في صفحة واحدة: إخفاق واحد مهما كان الترتيب. بكلفة 30 ns للتصفح تقضي القراءة المخلوطة 46 ميكروثانية في الترجمات وحدها وهو ما يكفي لـ100000 عملية جمع.
في Linux يمكنك طلب صفحات كبيرة بالصفحات الكبيرة الشفافة أو بصفحات hugetlbfs الصريحة وكثيرًا ما تثبّت أنظمة التداول جداولها الكبيرة فيها. يظهر الكسب في الجداول الكبيرة المقروءة بترتيب غير منتظم لا في الصغيرة ولا في المسوح بالترتيب.
تحتاج الصفحة الكبيرة إلى 2 ميغابايت من الذاكرة الفعلية المتجاورة وقد يصعب إيجادها على جهاز يعمل منذ أسابيع، والجدول الأصغر من صفحة يشغل الصفحة كلها مع ذلك. خصّصها مبكرًا عند بدء التشغيل للجداول القليلة التي تحتاجها.
ما ستبنيه
يتبع المختبر الدرس: صفحة العنوان وTLB باستبدال LRU وعدّ الإخفاقات والصفحات التي يمسّها تتبّع وعناوين السجلات بترتيب ما ومقارنة أحجام الصفحات والدراسة على سجلات SPY.
- الدالة · السؤالتعتمد على
- page_of(address, page_bytes)أي صفحة تحوي هذا العنوان؟القسمة الصحيحة
- TLB(entries).access(address, page_bytes)هل هذه الترجمة إصابة TLB؟page_of
- tlb_misses(trace, entries, page_bytes)كم إخفاق TLB يكلّف تتبّع؟TLB
- pages_touched(trace, page_bytes)كم صفحة مختلفة يمسّ تتبّع؟page_of
- record_trace(order, record_bytes)ما العناوين التي تمسّها قراءة السجلات بهذا الترتيب؟حساب بسيط
- compare_pages(trace, entries)كم تفيد الصفحات الكبيرة؟tlb_misses
- study(bars)ماذا توفّر الصفحات الكبيرة في سجلات SPY؟compare_pagespages_touchedrecord_trace
الموضوع التالي: كان حتى الآن برنامج واحد ونواة واحدة. في المشاركة الزائفة والحشو تكتب نواتان في متغيرين مختلفين يقعان في سطر الذاكرة المخبئية نفسه فيتنقل السطر ذهابًا وإيابًا بينهما. تعدّ عمليات الإبطال وتصلحها بالحشو.
المختبر: ما الذي يُقيَّم
7 مهام · 100 نقطة- صفحة عنوان10 نقطة
- TLB بسياسة LRU25 نقطة
- إخفاقات TLB لتتبّع10 نقطة
- الصفحات الممسوسة10 نقطة
- قراءة سجلات بترتيب10 نقطة
- صفحات 4 كيلوبايت مقابل 2 ميغابايت15 نقطة
- الدراسة على جدول SPY20 نقطة