⚡ Go Senior Mentor Engine | Phase 1: Week 1 - Day 2 (Master Edition)
Score: 0 / 15
Answered: 0 / 15
Accuracy: 0%
Phase 1: Week 1 Slice Internals Master Edition (15 Questions)

Day 2: Slice Internals, Underlying Arrays & Append Mechanics

ตำราเรียนรู้ระดับ Bare-Metal: ชำแหละโครงสร้างใต้กระโปรงของ Slice Header (Pointer, Len, Cap), ความสัมพันธ์กับ Underlying Array, กลไกการขยายความจุเมื่อใช้ append (Growth Strategy), และอันตรายของการแชร์ Underlying Array ในระบบ Ingestion สัญญาณชิปนักวิ่ง

1. Concept Mapping: JVM ArrayList vs. Go Slice Header Internals

1.1 ภาพจำที่ต้องล้างจากโลก JVM (Java `ArrayList` / Kotlin `MutableList`)

ในโลกของ JVM เมื่อคุณสร้าง List:

val checkpoints = ArrayList<Checkpoint>()

สัญชาตญาณของคุณคือ: ArrayList คือ Object บน Heap มีการห่อหุ้มข้อมูลด้วย Object Header, มีการจัดการ Array ภายในแบบ Object-based (เช่น elementData array บน Heap), และเมื่อขนาดเต็ม มันจะทำการสร้าง Array ใหม่บน Heap ขนาดใหญ่ขึ้น แล้วย้าย Reference ไปพร้อมกับโยน Object เก่าให้ GC ทิ้ง

⚠️ Spring Boot / JVM Mental Trap: โปรแกรมเมอร์สาย JVM มักมองว่า Slice ใน Go เป็น "Reference ไปยัง List" เหมือนใน Java แต่ความจริงแล้ว **Slice ไม่ใช่ Array และไม่ใช่ Pointer ไปยัง Array** แต่มันคือ Struct ขนาดเล็กที่เรียกว่า **Slice Header**

1.2 Go Runtime: โครงสร้างจริงของ Slice Header (24 Bytes)

ใน Go เมื่อคุณประกาศ s := make([]Checkpoint, 5, 10) ตัวแปร s มีขนาดเพียง **24 Bytes บนสถาปัตยกรรม 64-bit** (เก็บอยู่บน Stack เสมอ ถ้าไม่ Escape) โครงสร้างใต้กระโปรงคือ Struct SliceHeader:

type SliceHeader struct {
    Data unsafe.Pointer // 8 Bytes: ชี้ไปยังสมาชิกตัวแรกของ Underlying Array บน RAM
    Len  int            // 8 Bytes: จำนวนสมาชิกที่มีอยู่จริง (Length)
    Cap  int            // 8 Bytes: ความจุสูงสุดที่รองรับได้โดยไม่ต้องจองแอดเดรสใหม่ (Capacity)
}
  • Data Pointer: ชี้ไปยังตำแหน่ง RAM ของ Array ตัวจริงที่อยู่เบื้องหลัง
  • Len: บอกว่าตอนนี้มีข้อมูลกี่ตัว (ใช้สำหรับ Indexing และ Append)
  • Cap: บอกว่าจองพื้นที่เผื่อไว้กี่ตัว นับจาก Data Pointer ไปจนถึงจุดสิ้นสุดของ Underlying Array
คุณสมบัติ Java `ArrayList` Go Slice Header
Memory Footprint Object บน Heap + Array Object + Object Headers 24 Bytes บน Stack (Pointer + Len + Cap)
Pass by Value ส่ง Reference (ชี้ไปยัง Heap Object เดียวกัน) ก๊อปปี้ Slice Header 24 ไบต์ (Data pointer ยังชี้ Array เดิม)
Underlying Array ซ่อนอยู่หลังม่าน จัดการโดย JVM นักพัฒนาต้องเข้าใจความสัมพันธ์ระหว่าง Slice กับ Array ชัดเจน
2. Visual Architecture: Underlying Array Sharing & Sub-slicing Hazards

ในระบบรับสัญญาณ RFID นักวิ่ง s1 := pings[0:3] จะเป็นการสร้าง Slice ตัวใหม่ที่แชร์ Underlying Array ร่วมกับตัวเดิม นี่คือจุดที่ทำให้เกิดบั๊กข้อมูลทับซ้อนถ้าไม่ระวัง:

+-----------------------------------------------------------------------------------+ | UNDERLYING ARRAY SHARING & SUB-SLICING HAZARD | | | | Underlying Array on Heap / Stack (Capacity = 6): | | Index: [ 0 ] [ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ] | | Bib: [ 101 ] [ 102 ] [ 103 ] [ 104 ] [ 105 ] [ 106 ] | | | | Slice 1 (pings): | | Data ───> [ Points to index 0 ] | | Len = 6 | | Cap = 6 | | | | Slice 2 (batch := pings[1:4]): | | Data ───> [ Points to index 1 (Bib 102) ] | | Len = 3 (ครอบคลุม Index 1, 2, 3 คือ 102, 103, 104) | | Cap = 5 (นับจาก Index 1 ไปจนสุด Array คือ 5 ช่อง) | | | | ⚠️ อันตราย: ถ้าแก้ค่า batch[0] = 999 ค่าที่ Index 1 ของ Underlying Array | | จะถูกเปลี่ยนเป็น 999 ทันที ส่งผลกระทบต่อ pings[1] ด้วย! | +-----------------------------------------------------------------------------------+
⚠️ Bug สับขาหลอกจากการแชร์ Underlying Array:
เมื่อคุณทำ Sub-slicing (`s[low:high]`) ตัว Slice ใหม่จะแชร์ Underlying Array ร่วมกับ Slice เดิม การเขียนค่าทับลงใน Sub-slice จะส่งผลกระทบต่อข้อมูลใน Slice หลักทันที หากต้องการตัดขาด ให้ใช้เทคนิคการ Copy หรือ Full Slice Expression (`s[low:high:max]`) เพื่อจำกัด Capacity
3. Append Mechanics: กลไกการขยายความจุ (Growth Strategy) ของ `append`

เมื่อคุณใช้ฟังก์ชัน append เพิ่มข้อมูลลงใน Slice แล้วความจุ (Cap) เต็ม Go Runtime จะจัดสรร Underlying Array ใหม่ให้อัตโนมัติด้วยอัลกอริทึม Growth Strategy:

  • หาก Capacity เดิม น้อยกว่า 1,024: Go จะทำการ **X2 (เพิ่มเป็นสองเท่า)** ทันที
  • หาก Capacity เดิม มากกว่าหรือเท่ากับ 1,024: Go จะเพิ่มความจุทีละประมาณ **25%** (`cap + cap/4`) เพื่อป้องกันการจองหน่วยความจำเกินความจำเป็น
🎯 ผลลัพธ์ใน RAM: เมื่อเกิดการขยาย Cap เกินพื้นที่เดิม Go Runtime จะจอง Underlying Array ก้อนใหม่บน Heap, ทำการคัดลอกข้อมูลเก่าทั้งหมดไปใส่, และ Return Slice Header ตัวใหม่ที่ชี้ไปยัง Array ใหม่ ส่วน Array เก่าที่ไม่มีใครชี้จะถูกกวาดทิ้งโดย GC
🧠 Knowledge Verification: Master 15-Question Matrix (Slice Internals)
🛠️ Hands-on Engineering Lab 1.2: Safe Batch Splitter for Checkpoint Ingestion
Slice & Append Lab
📋 ข้อกำหนดทางเทคนิค (Technical Specification):
  1. สร้าง struct ชื่อ TimingEvent ประกอบด้วย Bib int64 และ TimeSec int64
  2. เขียนฟังก์ชัน BatchSplitter(events []TimingEvent, batchSize int) [][]TimingEvent เพื่อแบ่งสตรีมข้อมูลสถิตินักวิ่งออกเป็น Batch ย่อยๆ ตามขนาดที่กำหนด
  3. ต้องมั่นใจว่าแต่ละ Batch ที่ตัดออกมา ไม่แชร์ Underlying Array ร่วมกัน (เพื่อป้องกันการเกิด Data Race หรือการทับซ้อนของข้อมูลระหว่างการประมวลผลแบบ Concurrent)