علی فرهمند
علی فرهمند
خواندن ۲ دقیقه·۵ سال پیش

در اپلیکیشن مقیاس پذیر و کارا، جنگ، جنگ میلی ثانیه هاست

عوامل متعددی رو به عنوان دلایل سریع بودن زبان Go نام می برند که یکی از این موارد قابلیتی در کامپایلر به عنوان Function In-Lining است.

همانطور که می دانید فراخوانی توابع در هر زبانی یک عملیات رایج است که طراحان پردازنده ها دائما در حال بهینه سازی روال آن هستند ولی از overhead حاصل فراخوانی آنها گریزی نیست. با هر فراخوانی تابع یک stack frame جدید ایجاد و اطلاعات فراخواننده به صورت موقت ذخیره می شود، اطلاعات رجیستر ها نیز در stack ذخیره شده و محاسبات آدرس دهی جدیدی در CPU برای تابع مورد نظر انجام می شود. از جمله تکنیک های محدود نمودن این overhead تکنیکی با نام In-Lining (با عبارت Inlining نیز شناخته می شود) است. در این تکنیک بدنه تابعی که قابل Inline شدن می باشند همانند بخشی از فراخواننده اعمال و اجرا می شود. به عبارت ساده تر بدنه تابع در فراخواننده کپی می شود. این قابلیت تعاملات مابین stack و CPU را کاهش می دهد.

برای Inline بودن یک تابع مجموعه ای از شرایط ساده از جانب کامپایلر Go اعمال می شود که به عنوان نمونه اینکه شامل حدودا 40 عبارت، فارغ از هرگونه عبارت پیچیده مانند فراخوانی تابع، حلقه، select، switch و ... باشد.

به عنوان نمونه کد زیر

هنگامی که با دستور زیر اجرا نماییم

go run -gcflags='-m -m' main.go

خروجی زیر را نمایش می دهد:

# command-line-arguments
.\main.go:14:6: can inline add as: func(int, int) int { return x + y }
.\main.go:7:6: cannot inline main: function too complex: cost 98 exceeds budget 80
.\main.go:11:12: inlining call to add func(int, int) int { return x + y } ...

حال فرض کنید که کد مورد نظر را با کمی تغییر به صورت زیر بنویسیم:

در صورتی که مجددا تکه کد نمونه بالا را اجرا نماییم خروجی نمایش داده شده کمی متفاوت خواهد بود:

# command-line-arguments
.\main.go:18:6: cannot inline add: function too complex: cost 143 exceeds budget 80
.\main.go:19:16: inlining call to logrus.WithFields func(logrus.Fields) *logrus.Entry { return logrus.std.WithFields(logrus.fields) }
.\main.go:22:9: inlining call to logrus.(*Entry).Info method(*logrus.Entry) func(...interface {}) { logrus.entry.Log(logrus.Level(4), logrus.args...) }
.\main.go:10:6: cannot inline main: function too complex: cost 217 exceeds budget 80
.\main.go:11:18: inlining call to logrus.SetFormatter func(logrus.Formatter) { logrus.std.SetFormatter(logrus.formatter) }
...

همانطور که در دومین سطر از خروجی نمایش داده شده است، امکان Inline شدن تابع add به دلیل پیچیدگی وجود ندارد. حال تصور نمایید که این دست از توابع در یک حلقه به تعداد بالا قرار بر فراخوانی داشته باشند. (Segmented Stack و hot split problem را نادیده بگیریم).

نکته ای که وا داشت تا حداقل مطلبی کوتاه دراین مورد بیان کنم. در مواردی که دنبال رسیدن به کارایی بالا هستیم، توجه به توابع Inline ها خالی از لطف نیست. لازم نیست در هر شرایطی در هر تابعی که قابلیت inlining را دارا هستند، مکانیز های لاگینگ را قرار دهیم.

در اپلیکیشن های مقیاس پذیر و کارا، جنگ، جنگ میلی ثانیه هاست.








gogolangperformancescalability
   software engineer
شاید از این پست‌ها خوشتان بیاید