محل حافظه و عملکرد

اگرچه حافظه اغلب به عنوان یک مخزن ذخیره‌سازی واحد و یکنواخت در نظر گرفته می‌شود، اما سازماندهی فیزیکی آن و نحوه دسترسی CPU به آن تأثیر عمیقی بر عملکرد برنامه دارد. درک موقعیت مکانی حافظه، کلید نوشتن کد با کارایی بالا است که از سلسله مراتب حافظه پنهان CPU به طور کارآمد استفاده می‌کند.

سلسله مراتب حافظه نهان CPU

یک پردازنده مرکزی موبایل مدرن بسیار سریع‌تر از رم اصلی سیستم (DRAM) است. برای پر کردن این شکاف عملکرد، پردازنده‌ها از چندین سطح حافظه کوچک و بسیار سریع به نام حافظه پنهان (cache) استفاده می‌کنند.

  • حافظه نهان L1 (سطح 1) : کوچکترین و سریعترین (حدود 1 نانوثانیه). در یک پردازنده 3 گیگاهرتزی، این مقدار حدود 3 سیکل ساعت است.
  • حافظه نهان سطح ۲ (L2 Cache) : بزرگتر و کمی کندتر (حدود ۳-۵ نانوثانیه یا حدود ۱۰-۱۵ سیکل).
  • حافظه نهان سطح ۳ (L3 Cache) : بزرگترین حافظه نهان (حدود ۱۰ تا ۲۰ نانوثانیه یا حدود ۳۰ تا ۶۰ سیکل).
  • حافظه اصلی (DRAM) : بزرگترین و کندترین (حدود ۱۰۰ نانوثانیه یا حدود ۳۰۰+ چرخه).

هرم تأخیر حافظه

زمینه‌سازی تأخیر: هزینه‌ی یک وقفه

برای درک تأثیر این اعداد، یک CPU سوپراسکالر مدرن را در نظر بگیرید که می‌تواند ۴ تا ۸ دستورالعمل را در هر سیکل کلاک اجرا کند.

اگر CPU تمام حافظه‌های نهان را از دست بدهد و مجبور باشد ۱۰۰ نانوثانیه (۳۰۰ سیکل) برای خواندن یک DRAM صبر کند:

  • تعداد سیکل‌های از دست رفته : حدود ۳۰۰ سیکل.
  • دستورالعمل‌های «هدر رفته» : بین ۱۲۰۰ تا ۲۴۰۰ دستورالعمل که اگر داده‌ها از قبل در یک رجیستر محلی یا حافظه نهان L1 بودند، می‌توانستند اجرا شوند.

وقتی کد شما محل حافظه ضعیفی دارد، پردازنده لزوماً مشغول محاسبات پیچیده ریاضی نیست؛ بلکه اغلب "متوقف می‌شود" و در حالی که منتظر زیرسیستم حافظه است، برای هزاران معادل دستورالعمل بیکار می‌ماند.

دستورالعمل در هر چرخه (IPC)

یک معیار کلیدی برای اندازه‌گیری این کارایی، تعداد دستورالعمل‌ها در هر چرخه (IPC) است. IPC نشان می‌دهد که CPU به طور متوسط ​​در هر چرخه ساعت چند دستورالعمل را با موفقیت "کنار می‌گذارد" (تکمیل می‌کند).

  • IPC بالا (مثلاً 3.0 - 5.0) : پردازنده با راندمان بالا کار می‌کند و احتمالاً بیشتر داده‌های خود را در حافظه‌های نهان یا رجیسترهای L1/L2 پیدا می‌کند.
  • IPC پایین (مثلاً کمتر از 0.5) : پردازنده به شدت در تنگنا قرار دارد. حتی اگر پردازنده در مانیتورهای سیستم در حالت "استفاده" 100٪ باشد، در واقع بیشتر آن صرف انتظار برای حافظه می‌شود - حالتی که به عنوان توقف حافظه شناخته می‌شود.

محل حافظه عامل اصلی است که تعیین می‌کند آیا یک حلقه داده‌محور با IPC بالا اجرا می‌شود یا به مجموعه‌ای از وقفه‌ها تبدیل می‌شود.

خطوط حافظه پنهان

پردازنده‌ها بایت‌های تکی را از حافظه بارگذاری نمی‌کنند. در عوض، بلوک‌هایی با اندازه ثابت به نام خطوط حافظه پنهان (cache lines ) را بارگذاری می‌کنند که معمولاً ۶۴ بایت هستند. وقتی به یک متغیر دسترسی پیدا می‌کنید، پردازنده کل قطعه ۶۴ بایتی حاوی آن را به حافظه پنهان (cache) منتقل می‌کند.

مکانیک خط کش

TLB (بافر کناری ترجمه)

اندروید از حافظه مجازی استفاده می‌کند. هر دسترسی به حافظه نیاز به ترجمه یک آدرس مجازی به یک آدرس فیزیکی دارد. TLB یک حافظه پنهان تخصصی است که ترجمه‌های اخیر را ذخیره می‌کند. یک خطای TLB مستلزم آن است که هسته، جداول صفحه را در حافظه اصلی پیمایش کند، که در مقایسه با خطای TLB، عملیاتی نسبتاً پرهزینه است.


مشخصات سخت‌افزاری: پیکسل ۱۰ پرو فولد

برای تمرین‌های زیر، ما از یک دستگاه سخت‌افزاری Pixel 10 Pro Fold استفاده کردیم. این دستگاه دارای سیستم روی چیپ Google Tensor G5 است.

بازجویی از سخت‌افزار

برای درک زیرسیستم حافظه، ابتدا پیکربندی CPU و پارامترهای حافظه پنهان را بررسی می‌کنیم.

# Check CPU architecture and core parts
adb shell cat /proc/cpuinfo | grep 'CPU part' | sort -u
# Output:
# CPU part  : 0xd8b
# CPU part  : 0xd8c
# CPU part  : 0xd90

# Check cache line size
adb shell getconf -a | grep CACHE_LINESIZE
# Output:
# LEVEL1_ICACHE_LINESIZE             64
# LEVEL1_DCACHE_LINESIZE             64

رمزگشایی قطعات CPU

مقادیر CPU part در /proc/cpuinfo شناسه‌های هگزادسیمال برای هسته‌های CPU ARM هستند. برای Laguna SoC موجود در Pixel 10 Pro Fold، این مقادیر به صورت زیر نگاشت می‌شوند:

  • 0xd8b : ARM Cortex-A520 (هسته‌های کارآمد)
  • 0xd90 : ARM Cortex-A720 (هسته‌های عملکردی)
  • 0xd8c : ARM Cortex-X4 (هسته اصلی)

این پیکربندی ۴+۳+۱ در SoCهای موبایل مدرن رایج است، جایی که خوشه‌های مختلف ممکن است اندازه حافظه پنهان و تأخیرهای متفاوتی داشته باشند.


انواع محلات

طراحی کارآمد نرم‌افزار به دو نوع اصلی از موقعیت مکانی متکی است:

  1. مکان مکانی : اگر به یک مکان حافظه دسترسی پیدا شود، احتمالاً به مکان‌های حافظه مجاور نیز به زودی دسترسی پیدا خواهد شد. پیمایش ترتیبی آرایه نمونه کلاسیک این مورد است. از آنجا که CPU کل یک خط حافظه پنهان را بارگذاری می‌کند، دسترسی به عنصر بعدی در یک آرایه اگر از قبل در خط حافظه پنهان باشد، تقریباً "رایگان" است.
  2. محلی بودن زمانی : اگر به مکانی از حافظه دسترسی پیدا شود، احتمالاً به زودی دوباره به همان مکان دسترسی پیدا خواهد شد. الگوریتم‌های خوب، داده‌ها را در حالی که هنوز در حافظه پنهان "داغ" هستند، دوباره استفاده می‌کنند.

تمرین عملی: اندازه‌گیری موقعیت مکانی با simpleperf

در این تمرین، ما simpleperf برای نظارت بر شمارنده‌های عملکرد سخت‌افزار در حین اجرای دو پیمایش مختلف از یک ماتریس ۲۵۶ مگابایتی استفاده خواهیم کرد.

  1. پیمایش سطر به سطر : به عناصر ماتریس به ترتیبی که در حافظه ذخیره شده‌اند دسترسی پیدا می‌کند. این روش با حافظه پنهان سازگار است و از موقعیت مکانی بهره می‌برد.
  2. پیمایش ستون به ستون : برای دسترسی به عناصر بر اساس ستون، در حافظه پرش می‌کند. این روش اغلب حافظه پنهان و TLB را از دست می‌دهد و باعث می‌شود CPU از کار بیفتد.

۱. با Simpleperf اجرا کنید

فایل باینری را وارد کنید، مطمئن شوید که قابل اجرا است و simpleperf stat برای اندازه‌گیری رویدادهای حافظه پنهان و TLB استفاده کنید. ما از پسوند :u برای اندازه‌گیری رویدادها در فضای کاربری استفاده می‌کنیم. این دستورات برای دسترسی به شمارنده‌های PMU سخت‌افزاری در اکثر دستگاه‌ها به adb root نیاز دارند.

adb root
adb shell "chmod +x /data/local/tmp/LocalityLab"

مشخصات ردیف-رشته تحصیلی:

adb shell "simpleperf stat -e cpu-cycles:u,instructions:u,cache-misses:u,L1-dcache-load-misses:u,dTLB-load-misses:u /data/local/tmp/LocalityLab row"

ستون مشخصات - رشته تحصیلی:

adb shell "simpleperf stat -e cpu-cycles:u,instructions:u,cache-misses:u,L1-dcache-load-misses:u,dTLB-load-misses:u /data/local/tmp/LocalityLab col"

۲. اندازه‌گیری‌های نمونه (پیکسل ۱۰ پرو فولد)

نتایج زیر بر روی یک دستگاه سخت‌افزاری Pixel 10 Pro Fold اندازه‌گیری شده است:

متریک ردیف-ماژور (دوستانه) ستون اصلی (غیردوستانه) تفاوت
زمان اجرا ۰.۸۳ ثانیه ۶۸.۳ ثانیه ۸۲ برابر کندتر
دستورالعمل‌ها ۵.۲۷ میلیارد ۱۰.۲۰ میلیارد ۱.۹ برابر بیشتر
چرخه‌های پردازنده ۱.۲۰ میلیارد ۶۲.۱۸ میلیارد ۵۲ برابر بیشتر
دستورالعمل در هر چرخه (IPC) ۴.۴۰ ۰.۱۶ ۲۷ برابر راندمان کمتر
حافظه نهان داده سطح ۱ (L1 Data Cache) دچار مشکل می‌شود ۲۱۰ میلیون ۳,۳۶۹ میلیون ۱۶ برابر بیشتر از دست رفته
خطاهای بارگذاری dTLB ۰.۱۳ میلیون ۲,۸۸۸ میلیون ۲۲۰۰۰ برابر بیشتر از دست رفته

۳. تحلیل نتایج

  • افت IPC : در تست row-major، پردازنده به IPC برابر با ۴.۴۰ دست می‌یابد که نشان می‌دهد چندین دستورالعمل را در هر سیکل به طور کارآمد اجرا می‌کند. در تست column-major، IPC به ۰.۱۶ کاهش می‌یابد. این بدان معناست که پردازنده ۹۶٪ مواقع متوقف می‌شود و منتظر رسیدن داده از DRAM است.
  • گلوگاه TLB : چشمگیرترین تفاوت در خطای بارگذاری dTLB است. دسترسی ترتیبی (ردیف اصلی) در همان صفحات حافظه باقی می‌ماند و در نتیجه خطای TLB بسیار کمی رخ می‌دهد. پرش از ستون‌ها (ستون اصلی) باعث می‌شود CPU دائماً به صفحات جدید مراجعه کند، TLB را تحت فشار قرار دهد و پیمایش‌های پرهزینه جدول صفحه را مجبور کند.
  • بهره‌وری حافظه پنهان : پیمایش ستون اصلی، ۱۶ برابر خطای حافظه پنهان سطح ۱ بیشتر ایجاد می‌کند و CPU را مجبور می‌کند دائماً داده‌ها را از سطح ۳ یا DRAM که بسیار کندتر است، دریافت کند.

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


← موضوعات | ↑ بالا | مقیدسازی سرویس →