«این دیگه چیه؟ چرا کامپایلر دو بار w4 & w1 رو حساب کرده، بعد هم دو بار w0 & w4 رو تکرار کرده! مگه نمیشد یک mov بزنه و خلاص؟»
چیزی که در نگاه اول مثل یک باگ یا تنبلی کامپایلر به نظر میرسه، در واقع یکی از بهترین درسهای بهینهسازی CPUهای مدرنه!
قبلش باید ببینیم چرا GCC (و گاهی Clang) همچین کار «عجیبی» میکنن و چرا تقریباً همیشه تو این موارد حق با کامپایلره.
کد سی احتمالاً همچین چیزی بوده:
uint32_t lookup(uint32_t index, uint32_t mask, const uint32_t* table) {
uint32_t idx = index & mask;
return table[idx];
}و در نتیجه کد اسمبلی تولید شده توسط GCC همچین چیزیه!
and w0, w4, w1 // w0 = w4 & w1
and w2, w4, w1 //! w2 = w4 & w1
and w0, w0, w4 // w0 = w0 & w4
and w0, w0, w4 // !
ldr w0, [x2, w0, uxtw #2]
کدهای تکراری! ولی چرا؟ تو CPUهای مدرن ARM64 (Apple M1/M2/M3/M4، Snapdragon X Elite، Graviton و …) اجرای دوبارهی یک دستور سادهی AND تقریباً همیشه از mov سریعتره چون mov وابستگی داده (data dependency) ایجاد میکنه و pipeline را متوقف میکنه. یعنی وقتی مینویسی mov w2, w0،اتفاقی که میفته اینه که CPU باید صبر کنه تا w0 کاملاً آماده بشه، بعد w2 رو پر کنه. اما وقتی دو بار and w2, w4, w1 مینویسی، هر دو دستور ورودی یکسان (w4 و w1) داره و هیچ وابستگیای بینشون نیست. نکته بعدی اینکه CPUهای out-of-order میتونند این دو دستور AND رو همزمان در دو واحد ALU مختلف اجرا کنند و از طرفی مکانیزم Register Renaming باعث میشه CPU این دو نتیجه را بهعنوان دو رجیستر فیزیکی کاملاً جدا ببینه و هزینهی واقعی خیلی خیلی کمه!
جالب اینه که clang خروجی متفاوتی داره و کمی تمیزتره!
and w0, w1, w2
and w0, w0, w1
ldr w0, [x3, w0, uxtw #2]
دلیلش اینه که Clang کمی محافظهکارتره و بیشتر به «کد تمیز» اهمیت میده، تو بنچمارکهای واقعی روی M2/M3/M4 معمولاً تفاوت سرعت از ۱٪ کمتره. یعنی GCC با کد «زشتتر» گاهی حتی چند صدم درصد سریعتره!
پس یادتون نره در بیشتر مواقع «کامپایلر بیشتر از من میفهمه»، کد اسمبلی کوتاهتر هم نشونه سریعتر بودنش نیست. دفعهی بعدی هم که GCC یا Clang چیزی تولید کرد که در نگاه اول «احمقانه» به نظر رسید، قبل از آهکشیدن یه بنچمارک کوچیک بزنین و یه نگاهی به معماری همون CPU بندازید. تو ۹۹٪ مواقع میبینین کامپایلر حق داشته. البته چیزهایی که هکرهای ffmpeg دستی بهینه میکنن رو هم نمیشه نادیده گرفت، ولی اونها قرار نیست به ما ثابت کنن که کامپایلر خنگه و ما ازش باهوشتریم. و در نهایت هم یادمون نره که روی آرم، GCC معمولاً یه ذره، خیلی کم اسمبلی بهینهتری تولید میکنه.
📡openpcb
