تماس با ما

هرگونه پیشنهاد و انتقاد خود را با ما در میان بگذارید

یکی از مشکلاتی که در طراحی یک کامپیوتر وجود داره اینه که سرعت حافظه اصلی (RAM) کمتر از سرعت پردازشگره، این درحالیه که برنامه ها و داده ها همه در حافظه اصلی قرار دارن و به ازای تک تک دستوراتی که در حافظه قرار داره و باید اجرا بشه پردازشگر معطل میشه، اگه بشه کاری کرد که سرعت حافظه اصلی زیاد بشه این مشکل حل میشه.

اما یه مشکلاتی برای این راه حل وجود داره که کلا کامپیوتر ساز ها بی خیال این راه حل شدن

  • مشکل اول اینه که این کار خیلی هزینه بره و حافظه های سریع تر خیلی گرون در میاد و به صرفه نیست.
  • مشکل دوم مربوط به حجمه، چون فاصله فیزیکی بین حافظه اصلی و پردازشگر باعث کند شدن حافظه میشه، برای اینکه حافظه سرعت برابر سرعت پردازشگر داشته باشه باید اون رو منتقل کرد کنار خود پردازشگر، اما چون فضای فیزیکی زیادی رو اشغال میکنه مشکلاتی رو به وجود میاره به علاوه مشکل اول هم هنوز باقی مونده.

راه حل این مساله بر میگرده به یک اصل در برنامه های کامپوتری به اسم اصل محلی بودن مراجعات، این اصل میگه: احتمال اینکه به داده هایی که اخیرا بهشون مراجعه شده دوباره مراجعه بشه بیشتر از سایر داده هاست.

حالا یعنی چی؟ با یک مثال توضیح میدم: میدونید که برنامه های کامپیوتری اغلب شامل روال ها و حلقه ها هستن، حلقه ها رو برای این میسازن که یک یا چند دستور رو بیش از یک بار اجرا کنن، بنابراین دستوری که در حلقه اجرا میشه به احتمال زیاد در چند سیکل بعدی دوباره اجرا میشه، همچنین در مورد روال ها میشه گفت که اصلا روال ها بخش هایی از برنامه هستن که بهشون زیاد مراجعه میشه و برای همین از روال استفاده میکنن، پس در مورد برنامه ها تا حد زیادی اصل محلی بودن برقراره، در مورد داده های برنامه ها هم همینطوره (این رو تجربه و آمار نشون داده).

خب با توجه به اصل محلی بودن بهترین راه حل اینه که یک حافظه خیلی سریع اما کوچک رو در داخل پردازشگر تعبیه کنیم و هر بخش از حافظه که میخایم استفاده کنیم به اون منتقل کنیم ( این کار با سرعت کمتر حافظه اصلی انجام میشه) ، حالا با توجه به اصل محلی بودن مراجعات بعدی، به احتمال زیاد در آینده نزدیک باز هم به این بخش از حافظه مراجعه میشه اما دفعه بعد که به این داده ها رجوع میشه، این بخش در حافظه سریع تر کپی شده پس سرعت دسترسی افزایش پیدا میکنه، به این حافظه سریع کش (cache) یا همون حافظه نهان پردازشگر میگن.

دقت کنید که کش جای حافظه اصلی رو نمیگیره و فقط محلی برای ذخیره موقت داده ها تا سرعت دسترسی به اونا زیاد بشه در ضمن اگر محتوای کش تغییر بکنه باید راهی برای به روز رسانی حافظه اصلی پیدا کنیم.

حالا سوال اینه که چطور این کار رو بکنیم؟

روش هایی برای این کار هست که 3 تای اونا رو من اینجا آوردم:

 

روش اول، استفاده از حافظه های انجمنی(تداعیگر-associative cache memory):


در این روش از یک حافضه انجمنی استفاده میکنیم ( اگه یادتون رفته حافظه انجمنی چیه اینجا کلیک کنید)، به شکل نگاه کنید تا توضیح بدم:

شمای کلی حافظه کش انجمنی

 همانطور که میبینید هر خانه از حافظه کش دو بخش داره که به کمک یک خط از هم جدا شدن:

  • بخش داده : در این بخش دقیقا همون چیزی قرار داره که در حافظه RAM قرار داره.
  • بخش آدرس:در این بخش آدرس داده در حافظه RAM قرار میگیره.

مثلا در شکل بالا اون بخش از حافظه کش که با کادر قرمز مشخص شده میگه که در حافظه به آدرس (00FB86C7H) مقدار عددی (22FDD915H) قرار داره که در کش ذخیره شده.

روش کلی کار به این صورته که وقتی که پردازنده میخاد یک آدرس از حافظه رو بارگیری کنه اول آدرس اون رو برای حافظه انجمنی میفرسته و در حافظه انجمنی بخش Data ماسک میشه و بخش آدرس به صورت همزمان با همه سلول های حافظه کش مقایسه میشه (یادتون نره که این حافظه ها از نوع انجمنی-CAM هستن و قابلیت این کار رو دارن)  و اگه محتوای آدرس با آدرسی که پردازنده فرستاده مطابقت داشته باشه مثل کادر قرمز رنگی که در عکس مشخص شده مقدار Data به خروجی حافظه انجمنی ارسال میشه اما اگر پیدا نشه با توجه به اینکه به صورت همزمان این آدرس برای RAM هم ارسال شده پردازنده منتظر میمونه تا محتوای RAM از راه برسه، وقتی که محتوای RAM رسید اون رو در یک خانه خالی از کش (یعنی همون خانه هایی که بیت valid اونا که در سمت چپ مشخص شده مقدار 0 رور داره) ذخیره میکنه و آدرس اون داده رو هم در بخش آدرس سلول کش ذخیره میکنه  تا دفعات بعدی از اطلاعات کش استفاده کنه و اتلاف وقت کمتر بشه، برای اطمینان بیشتر (با توجه به اصل محلی بودن) میشه یک بلاک (block) از چند تا آدرس کنار این آدرس رو هم در کش بار کرد.

دقت کنید که اگه حافظه کش پر شده باشه باید پردازنده برای جایگزینی خانه های پر یه روش انتخاب کنه مثلا میتونه از روش FIFO استفاده کنه یعنی هر سلولی که زودتر پر شده زودتر هم جایگزین میشه یا از الگوریتم LRU استفاده کنه که به این معنیه که هر خانه ای که اخیرا کمتر استفاده شده رو استفاده کن.

نکته: معمولا بیش از یک خانه حافظه رو به کش منتقل میکنن چون وفتی که به یک آدرس از حافظه دسترسی صورت بگیره به احتمال زیاد به آدرس های اطراف اون آدرس هم در آینده نزدیک نیاز میشه.

روش دوم، روش نگاشت مستقیم(direct mapped):

 چون استفاده از حافظه های انجمنی خیلی گرون در میاد یه معماری دیگه که پیشنهاد میشه استفاده از حافظه های معمولیه، اما جستجو کردن در این حافظه ها زمانگیره و نه تنها مشکل اختلاف سرعت رو حل نمیکنه، تازه برای سیستم سربار هم به وجود میاره، اما یه راه حل ساده وجود داره و اونم اینه که هر آدرس از حافظه همواره در یه جای خاص از حافظه کش ذخیره بشه، به این ترتیب دیگه نیاز به جستجو نیست و پردازشگر دقیقا میدونه کجا دنبال اون داده بگرده، شکل رو ببینید تا توضیح بدم:

نگاشت مستقیم کش

 در این وضعیت هر آدرس حافظه رو به دو بخش تقسیم میکنیم، بخش کم ارزش تر به اسم index و بخش پر ارزش تر به اسم tag، زمانی که پردازنده میخاد به یک آدرس از حافظه دسترسی پیدا کنه اول در آدرس index از حافظه ی کش بررسی میکنه که آیا بخش آدرس برایر tag هست یه نه، اگر برابر بود که داده اون بخش همون داده ذخیره شده در اون آدرس خاص از حافظه است، اما اگه نبود بازم مثل روش قبل منتظر میمونه تا جواب درخواستی که برای حافظه فرستاده شده برسه، وقتی که اطلاعات درخواستی رسید، برای دفعات بعد در حافظه کش ذخیره میشه، دقت کنید که مثل حالت قبل در اینجا هم میشه اطلاعات رو به صورت بلوکی منتقل کید .

 مثلا فرض کنید که در شکل پردازنده میخاد آدرس 0x00fbfffeH رو بارگذاری کنه، در شکل مشخصه که طول index برابر 16 بیته پس با توجه به اینکه هر رقم در مبنای hex برابر 4 بیت میشه داریم:

index=0xfffeH

tag=0x00fbH

و در نهایت block number=0xfffH و offset=0xbH

به عبارت دیگه پردازنده در بخش tag از آدرس 0xfffeH از حافظه کش به دنبال مقدار 0x00fbH میگرده اگر برابر بود از بخش داده استفاده میکنه اما اگر نبود به سراغ حافظه میره.

یکی از مشکلاتی که این روش داره اینه که اگه از شانس بد ما به تناوب از دو محل حافظه که index یکسانی دارن بخواهیم استفاده کنیم دائم مجبوریم که محتوای کش رو بروز کنیم که در این صورت سرعت به شدت افت میکنه.

مثلا فرض کنید که در یک سیستم چند برنامه یکی از برنامه ها از آدرس 0x00fbff00H تا 0x00fbffffH و برنامه دیگه از آدرس 0x1200ff00H تا 0x1200ffffH ذخیره شده باشن، اگر دقت کنید بخش index هر دو برنامه در یک بازه هست (هر دو از 0xff00H تا 0xffffH )، اگر از شانس بد ما این دو برنامه بخاد هم زمان اجرا بشه، (در یک سیستم چند برنامه همیشه این امکان وجود داره) سرعت اجرا به شدت افت میکنه.

برای اینکه احتمال رخ دادن این مشکل رو به حداقل برسونیم دو تا راه حل وجود داره:

  • یک راه اینه که ظرفیت حافظه کش رو افزایش بدیم، در این صورت تعداد بیت های بیشتری به بخش index اضافه میشه و تعداد بیت های بخش tag کمتر میشه، در نتیجه تعداد سلول کمتری از حافظه به یک خانه از کش نگاشت میشه( واضحه که تعداد خانه هایی که به یک خانه از حافظه کش نگاشت میشه برابر 2 به توان تعداد بیت های بخش tag است)، و در نتیجه احتمال بروز مشکل هم پوشانی کمتر میشه، البته اینکار با هزینه زیاد خواهد بود.
  • راه دیگه اینه که اندازه بلاک رو کم کنیم، در این صورت چون بلاک های کوچکتری رو به حافظه کش منتقل میکنیم احتمال پاک شدن اطلاعات قبلی کمتر میشه البته کوچک کردن اندازه بلاک یک اثر منفی هم داره و اونم اینه که اصل محلی بودن رو نادیده میگیره.
روش سوم، نگاشت تداعیگر مجموعه ای(set associative cache memory):

راه حل هایی که گفتیم هر کدام مشکلی بنابراین یک راه حل دیگه که پیشنهاد میشه اینه که هر index رو به بیش از یک خانه حافظه اختصاص بدیم، در این روش هر خانه از حافظه کش به بیش از یک خانه از حافظه مربوط میشه مثلا در شکل میبینید که در هر خانه از حافظه کش 2 آدرس از حافظه اصلی ذخیره شده و زمانی که پردازشگر به دنبال اطلاعاتی در این آدرس از حافظه کش میگرده به طور همزمان آدرس tag با هر دو tag ذخیره شده در این آدرس از کش مقایسه میشه:

حافظه تداعیگر مجموعه ای

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

 

حافظه های کش چند سطحی:

روش هایی که گفتم همه مربوط هستن به یک دسته از حافظه های کش که به کش یک سطحی معروف هستن، در این مدل از کش ها یک حافظه کش سریع بین پردازنده و حافظه اصلی قرار میگیره، اما به دلیل قیمت گرون حافظه کش که با سرعت پردازنده بتونه کار کنه امروزه معماری های چند سطحی متداول شده، در معماری های چند سطحی هر سطح از حافظه کش به سطح پایین خود به دید پردازنده نگاه میکنه و به سطح بالاتر از خود به دید حافظه و معماری هم به یکی از این سه حالتی که گفتم هست، مثلا در شکل زیر یک معماری دو سطحی رو میبینید که سطح اول ازنوع حافظه انجمنی باظرفیت 16 کیلوبایت و سطح دوم ازنوع نگاشت مستقیم با حجم 2 مگابایت هست، اگر پردازنده دنبال آدرسی در سطح 1 بگرده و اون رو پیدا نکنه اول میره سراغ سطح 2 و اگر اونجا نبود میره سراغ حافظه اصلی.

حافظه کش دوسطحی

 البته روش های دیگه ای هم برای چند سطحی کردن کش وجود داره، مثلا میشه برای دستور العمل ها و داده ها کش های جداگانه در نظر گرفت و تعداد سطح ها رو هم افزایش داد، در ضمن در پردازنده های چند هسته ای هم میشه برای هر هسته یه کش جداگانه در نظر گرفت یا اینکه همگی از یه کش استفاده کنن که بحث جالب و پیچیده ایه که از سطح این مطلب فراتره.

نوشتن اطلاعات بر روی کش:

مطلب آخری که میخام بگم مربوط به نوشتن اطلاعات بر روی کشه، هر زمان که قراره اطلاعاتی بر روی حافظه کش نوشته بشه اطلاعات قبلی پاک میشه و این مهمه که این اطلاعات قبل از پاک شدن به حافظه اصلی بر گردونده بشه، راه های مختلفی برای این کار وجود داره مثلا میشه وقتی که اطلاعات کش تغییر کرد همون موقع اطلاعات حافظه هم به روز رسانی بشه، به این روش کامل نویسی (write through) میگن، یا میشه فقط زمانی که قراره بر روی یک خانه از کش اطلاعاتی نوشته بشه محتوای قبلی در صورت تغییر به حافظه منتقل بشه، به این روش پس نویسی (write back) میگن، در این صورت باید یک بیت در کش تعبیه بشه تا زمانی که تغییری در کش ایجاد میشه اون بیت تغییر رو نشون بده.

 


لینک برای اطلاعات بیشتر:

https://en.wikipedia.org/wiki/CPU_cache

http://bytegate.ir

 http://hardware.itpro.ir