تو کامپیوتر CPU مسئول اجرای دستورات هستش و برای بازیابی دستورات و داده های به RAM نیاز داره. CPU توانایی زیادی در اجرای دستورات داره و سرعت دسترسی به داده های RAM مناسب با سرعت پردازش اونها نیست. به همین دلیل از یکسری تکنیک برای بهینه کردن فرآیند ها استفاده میکنه.
تکنیکها:
تکنیک Out-of-order execution: یک از تکنیک های CPU برای بهینه کردن زمان اجرا برنامه هاست و بجای اینکه منتظر دادههای دستور جاری بشه. دستورات بعدی را که قابلیت اجرا داره رو اجرا میکند.
تکنیک Branch predictor: یک مدار دیجیتال هستش برای پیش بینی بلاکی که قراره در دستورات شرطی اجرا بشه. در ابتدا شاید زیاد کار آمد به نظر نرسه ولی با گذشت زمان و نگهداری از تاریخچه و … احتمال انتخاب درست افزایش پیدا میکنه و در زمان اجرا صرفه جویی میکند.
تکنیک Speculative execution: یکی دیگر از تکنیک های بهینه سازی زمان در CPU هستش. این تکنیک در زمان های انتظار خود شروع به اجرای و نگهداری نتیجه دستوراتی میکند که هنوز زمان اجرای انها فرا نرسیده. با این کار وقتی لازم به اجرا آن دستورات باشد از قبل آنها اجرا شده و نتیجه مشخص است.
تکنیک Caching: یکی دیگر از تکنیک های افزایش سرعت پردازش است که نتیجه داده های محاسبه شده یا برخی داده های ضروری در یک حافظه بسیار پرسرعت در کنار پردازنده ذخیره میشوند.
وقتی بیش از یک هسته شروع به اجرای دستورات و تغییر دادهها میکند. اونها دادههای موجود تو cache رو با هم تغییر میدن.
با توجه به memory hierarchy، در پردازش های موازی زمانی که داده ای بین core ها مشترک هستش ما نمیتونیم بگیم که همیشه همه کارها به همون ترتیبی که نوشتن شدن اجرا میشن. دلیل اصلی هم memory visibility و reordering هستش.
قبل از شروع به مثال بالا نگاه کنید و فرض کنید که پردازنده thread ها رو توی core های متفاوتی اجرا میکنه.
در پردازنده های جدید دستور نوشتن بلافاصله بعد از صادر شدن اجرا نمیشه. در واقع CPU دوست داره این دستورات رو بزاره تو صف توی special write buffer (بافر مخصوص نوشتن). و بعد از مدتی همه اونها رو یک مرتبه توی حافظه اصلی بنویسه.
این به این معنی هستش که وقتی در یک thread داده ای تغییر میکنه، هیچ تضمینی نیست که توی باقی thread ها هم اون داده تغییر کنه. به عبارت دیگه در مثال بالا ممکنه reader thread تغییرات داده ها رو بلافاصله یا با تاخیر دریافت کنه یا هرگز دریافت نکنه.
داده ها در یک thread به همون ترتیبی که توی یک thread دیگه داده تغییر میکنه تغییر رو دریافت نمیکنن. این یک روش بهینه سازی برای بهبود عملکرد هستش. قسمت های مختلف این بهینه سازی رو انجام میدن. و بهش میگیم reordering.
هدف volatile اطمینان از بروز شدن متغیرها توی باقی فضاهای حافظه استفاده هستش. با استفاده از این modifier برنامه در حال اجرا به پردازنده میگه که این متغیر و هر جایی که ازش استفاده شده رو reorder نکن. همچنین بلافاصله بعد از تغییر این متغیر همه cache ها رو بروز کن.
در برنامه نویسی multi thread دوتا قانون باید رعایت بشه.
متدها یا بلاک های synchronized هر دوی این شرایط رو برای ما فراهم میکنن. البته با کاهش پرفورمنس.
حالا زمانی که ما میخواییم چندین thread بتونن یک بلاک کد رو به صورت موازی اجرا کنن و لازمه مطمئن باشیم که همه از تغییرات متغیرهای مشترک آگاه هستن باید از volatile استفاده کنیم. تا مشکل visibility داده ها رو برای ما حل کنه.
استراتژی Happens-Before Ordering: این استراتژی در رابطه به متغیر های volatile میگه؛ نوشتن توی متغیر های volatile قبل از خوندن اونها اتفاق مییوفته. این یک قانون درباره متغیر های volatile در Java Memory Model هستش JMM.
تکنیک Piggybacking: با توجه به استراتژی Happens-Before در ترتیب حافظه. در بعضی شرایط ما میتونیم visibility یک متغیر رو piggyback (کول کردن - به دوش کشیدن) کنیم روی یک متغیر volatile دیگه.
تو مثال بالا ما هر دو متغیر رو volatile کردیم. که این کار باعث کاهش کارایی برنامه میشه.
استراتژی Happens-Before برای این مثال میگه: همه چیز باید بعد از تغییر متغیر ready و قبل از خوندن مقدار این متغیر visible باشه.
اما با توجه به این استراتژی ما میتونیم متغیر number رو به صورت معمولی تعریف کنیم و تغییرات اون piggyback کنیم روی ready یا به عبارت دیگه بر روی دوش متغیر ready اطلاعات این متغیر رو جابجا کنیم.
با توجه به این مفاهیم و با کمتر تعریف کردن متغیر های volatile ما میتونیم علاوه بر گارانتی کردن visibility کد بهینه تری هم بنویسیم.