Streamهای جاوا ۸ چقدر سریع هستند؟ (قسمت دوم)

در مقالهی گذشته در مورد مقایسه کارایی استریمهای ترتیبی جاوا ۸ و حلقههای for جاوا در انجام یک عملیات یکسان صحبت کردیم. در این مطلب نتایج به دست آمده را تحلیل میکنیم.
در مطلب مقایسه کاراییها در یک نمونه آزمایش نتایج به دست آمده نشان داد که حلقه for به طور تقریبی ۱۵ برابر از استریمهای ترتیبی سریعتر هستند. واکنشهای متفاوتی به این مطلب وجود داشت. از ابراز شگفتی تا عدم باور و انکار صحت نتایج به دست آمده! عدهای هم به این نتیجه رسیدند که اصلا استریمها به درد نمیخورند. رسیدن به چنین نتیجهای از یک نتیجه بنچمارک به تنهایی کلا قابل توجیه نیست! چرا که باید چندین مورد و چندین مجموعه مورد آزمون قرار گیرد تا بتوان چنین حکم کلی صادر کرد. اما بگذارید نتایج را این بار بررسی کنیم.
اول باید بگوییم که نتایج به دست آمده اصلا جای تعجب نداشت. در این آزمایش ما نیم میلیون داده را از حافظه میخواندیم و بعد از دریافت مقادیر یک مقایسه دو به دو انجام میدادیم که بعد از کامپایل توسط JIT بیشتر از یک دستور اسمبلی را شامل میشود. به همین دلیل نتایج آزمون تحت تاثیر هزینه خواندن از حافظه و پیمایش است و چون خواندن از حافظه وابسته به سخت افزار است نتایج از یک پلتفورم به پلتفورم دیگر متفاوت خواهند بود.
این حقیقت که حلقه for استریم را در تست ما شکست داد هم عجیب نیست. ما عمدا در این آزمون شرایط فوقالعاده را انتخاب کردیم. این شرایط از چند جهت قابل بررسی هستند:
۱- ما حلقههای for را با استریم مقایسه کردیم.
حلقهها JIT-friendly هستند. کامپایلرها بیش از ۴۰ سال تجربه بهینهسازی حلقهها را دارند و ما نیز حلقهای را انتخاب کردیم که بسیار قابلیت بهینه شدن در کامپایل را دارد. دقیقا بر خلاف استریمها که استفاده از آنها به معنای فراخوانی یک فریم ورک اساسی است که سربار زیادی خواهد داشت. کامپایلر میتواند تا حدی سربار را کم کند اما نه کامل.
۲- ما یک دنباله از المانهای primitive را با یک دنباله از المانهای refrence مقایسه کردیم.
آرایه اعداد صحیح به اصطلاح cache friendly هستند. یک مجموعه از refrenceها حتی اگر مجموعه مبتنی بر آرایه باشد شانس کمتری برای cache شدن دارد. هر دسترسی به یک المان دنباله نیازمند derefrence کردن یک اشارهگر و دسترسی به آن حافظه است که نشاندهنده یک cache miss هست. پس با توجه به cache-friendly بودن آرایه اعداد صحیح کارایی آن از استریمهای ترتیبی مسلما بیشتر خواهد بود.
۳- ما استفاده سبک از CPU را با استفاده سنگین از CPU مقایسه کردیم.
به طور دقیقتر ما مقایسه دودویی را روی المانها انجام میدهیم. اگر روی هر المان دنباله یک عملیات سنگین و هزینه بر از نظر CPU را انجام میدادیم نتایج آزمون تحت تاثیر سرعت و قابلیت CPU شما در میآمد و تاثیر دیگر جوانب مانند cache missها و حلقههای کامپایلشده JIT ناچیز میشد.
نتیجهگیری:
از این مطلب و قسمت اول آن نکتهای که میتوان با خود به خانه برد این است که استریمهای ترتیبی میتوانند به طور چشمگیری از حلقههای for در بعضی مواقع آهستهتر باشند اما سایر مواقع اختلاف قابل ملاحظهای وجود ندارد. در هر صورت اگر شما استفاده از استریمها را ترجیح میدهید برای سبک کدنویسی خاص آنهاست و نه بهبود کارایی برنامهتان و در این زمان هم دلیلی برای دوری کردن از استریمها به دلیل مختل کردن کارایی برنامهتان وجود ندارد.
در بیشتر موارد استریمهای ترتیبی از حلقه for آهستهتر خواهند بود. اما در موارد بسیار خاصی این کاهش سرعت بسیار قابل توجه است و در اکثر مواقع اختلاف کارایی قابل قبول است. به خصوص که احتمالا برای این تعداد زیاد داده مورد استفاده قرار نخواهند گرفت و برای تعداد محدود داده اختلاف سرعت در حد چند نانوثانیه خواهد بود که کسی متوجه نخواهد شد.
منبع:
https://jaxenter.com/follow-up-how-fast-are-the-java-8-streams-122522.html