هذا المقال يتطلب مستوى محترف. لو لسه بتبدأ في لغة منخفضة المستوى زي Go أو C ومش متعوّد على مفاهيم الذاكرة، ابدأ بمقال أبسط الأول ثم ارجع لهنا.
Struct Padding: ليه الـ struct بتاخد 24 بايت وانت حاطط 17 بايت بس
لو عندك مصفوفة من 10 مليون struct بتاكل 240 ميجا رام، إعادة ترتيب الحقول جواها بتنزّل الاستهلاك لـ 160 ميجا. من غير ما تشيل ولا حقل واحد. الفرق كله سببه حاجة اسمها Struct Padding.
المشكلة باختصار
المبرمج بيفترض إن حجم الـ struct = مجموع أحجام حقوله. ده غلط. الكومبايلر بيحشر بايتات فاضية بين الحقول علشان يحاذيها في الذاكرة. النتيجة: struct فيه 10 بايت بيانات فعلية ممكن ياخد 24 بايت في الرام. على مصفوفة كبيرة، الـ 14 بايت الضايعة دي بتتضاعف ملايين المرات.
الفكرة بمثال قبل الكلام العلمي
تخيّل رف فيه خانات، كل خانة بتسع 8 صناديق بالظبط. عندك صندوق صغير (بايت واحد)، وصندوق كبير لازم يبدأ من أول خانة جديدة (8 بايت)، وصندوق صغير تاني.
لو رصّيتهم بالترتيب ده: صغير، ثم كبير، ثم صغير — هتحط الصغير الأول، وبعدين الكبير مش هينفع يبدأ بعده على طول لأنه لازم يبدأ من بداية خانة، فهتسيب فراغ. النتيجة رف مليان فجوات. لكن لو حطيت الكبير الأول ثم الصغيرين ورا بعض، هتملا الرف أحسن بكتير. نفس الصناديق، ترتيب مختلف، مساحة أقل.
القاعدة علميًا: المحاذاة (Alignment)
المعالج مابيقراش الذاكرة بايت بايت. بيقراها في كلمات (words) بحجم ثابت، غالبًا 8 بايت على معمارية 64-bit. علشان القراءة تتم في دورة واحدة، كل نوع لازم عنوانه يكون من مضاعفات حجمه. ده اسمه natural alignment.
boolوint8: محاذاة 1 — يقدروا يبدأوا في أي عنوان.int32: محاذاة 4 — لازم يبدأ من عنوان من مضاعفات 4.int64و pointer: محاذاة 8 — لازم يبدأ من عنوان من مضاعفات 8.
والـ struct نفسه حجمه بيتقرّب لأعلى ليبقى من مضاعفات أكبر محاذاة جواه (trailing padding)، علشان لو عملت منه مصفوفة يفضل كل عنصر محاذى صح.
الكود اللي بيوريك الفرق
الكود ده شغّال على Go 1.23 على معمارية amd64. بنقيس الحجم الفعلي بـ unsafe.Sizeof:
package main
import (
"fmt"
"unsafe"
)
// ترتيب سيء: صغير، كبير، صغير
type BadLayout struct {
a bool // 1 بايت + 7 padding علشان b يحاذي على 8
b int64 // 8 بايت
c bool // 1 بايت + 7 trailing padding
} // الإجمالي: 24 بايت
// ترتيب جيد: الكبير الأول ثم الصغار
type GoodLayout struct {
b int64 // 8 بايت
a bool // 1 بايت
c bool // 1 بايت + 6 trailing padding
} // الإجمالي: 16 بايت
func main() {
fmt.Println("BadLayout :", unsafe.Sizeof(BadLayout{})) // 24
fmt.Println("GoodLayout:", unsafe.Sizeof(GoodLayout{})) // 16
}