حذف بررسی مرز آرایه در Go با unsafe
خلاصهٔ کاملتر
به گفتهٔ نویسنده، حذف bound check یا BCE یکی از قویترین و پربازدهترین تکنیکهای بهینهسازی تو دنیای Go هست و معمولاً اولین کاریه که موقع بهینهسازی یه مسیر داغ امتحان میکنه. اول توضیح میده bound check چیه: Go یه زبان امنه و تضمین میکنه نمیتونی به عنصر بیرون از محدودهٔ اسلایس دسترسی پیدا کنی، برای همین کامپایلر یه مشت کد اسمبلی اضافه میکنه که موقع دسترسی خارج از محدوده، رانتایم پنیک کنه.
نویسنده میگه این تکنیک برای این قویه که تعداد دستورها و شاخهها (branch) رو تو مسیر داغ کم میکنه. همین کاهش خودش سیکلهای تلفشده رو کم میکنه، ولی سود دیگهای هم داره: اگه کدت از قبل با cache miss دستوپنجه نرم میکنه، کمتر شدن تعداد دستورها به کش L1 و uop cache و حتی فشار روی رجیسترها کمک میکنه.
اول روش معمول رو نشون میده: کامپایلر خیلی وقتها bound check رو حذف میکنه اگه بهش «ثابت» کنی که لازم نیست. راهش اینه که قبل از حلقه به کران بالا یا پایین دسترسی بگیری. مثلاً با نوشتن شرط حلقه به شکل i
func loadU32LE(b []byte, i uint) uint32 {
return *(*uint32)(unsafe.Add(unsafe.Pointer(unsafe.SliceData(b)), i))
}اینجا unsafe.SliceData پوینتر عنصر اول رو بدون ایجاد bound check میده، unsafe.Add معادل &b[i] میشه و در آخر به *uint32 کست و دیرفرنس میشه. نویسنده میگه اگرچه ظاهراً سه فراخوانی اضافه شده، کامپایلر همه رو inline میکنه و اسمبلی نهایی فقط به یه MOV و RET خلاصه میشه.
اما دوتا هشدار مهم میده. اول اینکه امضای تابع عوض شده و باید loadU32LE(data, offset) صداش کنی، نه data[offset:]، وگرنه سمت فراخوان یه چک جدید درست میشه. دوم اینکه این ترفند فقط روی معماریهای little-endian کار میکنه، برای همین با تگ go:build محدود شده. و از همه مهمتر: فقط جایی این کارو بکن که خودت میتونی ثابت کنی چک لازم نیست؛ اگه نمیتونی ثابت کنی، دست بهش نزن، چون کامپایلر بیدلیل نذاشتتش.
نکات کلیدی:
- bound check کدیه که Go برای جلوگیری از دسترسی خارج از محدودهٔ اسلایس اضافه میکنه
- حذفش تعداد دستورها و شاخهها رو کم میکنه و به کش و رجیسترها هم کمک میکنه
- روش معمول: با دسترسی زودهنگام به کران، به کامپایلر هینت بده (مثل i




