ตลอดสัปดาห์ที่ 1 เราได้ถอดรื้อภาพจำที่คุ้นเคยในโลก JVM (Kotlin / Java / Spring Boot) ออกทั้งหมด เพื่อสร้างสัญชาตญาณแบบ Go Systems Engineer ที่มองทะลุลงไปถึงระดับ Cache Line, CPU Word และ Operating System:
| หัวข้อสถาปัตยกรรม | JVM / Kotlin / Spring Boot | Go Bare-Metal (Idiomatic Go) |
|---|---|---|
| 1. Object & Memory Allocation | ทุกอย่างเป็น Reference ชี้ไปที่ Heap มี Object Header 16-byte บวมทุกตัว พึ่งพา GC เป็นหลัก | Value Type เป็นพลเมืองชั้นหนึ่ง (อยู่บน Stack Frame ถ้าไม่ Escape) ไม่ใช้ Pointer พร่ำเพรื่อ |
| 2. Dynamic Collections | ArrayList คือ Heap Object ห่อหุ้ม Object Array ภายใน มีปัญหา Pointer Chasing |
Slice Header (24B) เก็บ Data, Len, Cap ชี้ไปที่ Contiguous Backing Array ใน RAM |
| 3. Hash Maps | HashMap เป็น Array of Nodes (Linked-list/Red-Black Tree) แต่ละโหนดมี Header แยก |
`hmap` + Array of `bmap` (8-slots) เก็บข้อมูลเรียงติดกันเพื่อ Cache Locality และใช้ tophash กวาดสแกนเร็วระดับนาโนวินาที |
| 4. Struct Alignment | HotSpot JVM สลับลำดับฟิลด์ให้อัตโนมัติ (Field Reordering) นักพัฒนาไม่รู้ Layout จริงใน RAM | Go จัดวางไบต์ตามลำดับที่เขียน 100% ต้องเรียง Descending Alignment เพื่อกำจัดช่องว่าง Padding |
| 5. Strings & Buffers | java.lang.String เป็น Heap Object และการแปลงเป็น byte[] ต้องคัดลอกข้อมูลเสมอ |
StringHeader (16B) เป็น Read-only Slice of Bytes รองรับเทคนิค Zero-Copy ด้วย unsafe |
สรุปวิธีเขียนและใช้งานคำสั่งหลักทั้งหมดของ Go Week 1 เทียบกับ Kotlin/Java:
// ประกาศตัวแปรแบบ Type Inference
amount := int64(50000) // ตัวแปรอยู่บน Stack
// เครื่องหมาย & (Address-of) เพื่อหาแอดเดรสใน RAM
ptr := &amount // ptr คือ *int64 (ขนาด 8 Bytes)
// เครื่องหมาย * (Dereference) เพื่ออ่านหรือแก้ค่าที่แอดเดรสนั้น
*ptr = 60000 // ค่าของ amount เปลี่ยนเป็น 60000 ทันที
// ฟังก์ชันแบบ Pass by Value (Share Down, Copy Up)
func DeductFee(bal int64) int64 { return bal - 10 } // สำเนาค่า (ปลอดภัย ไม่มี Side-effect)
func MutateBalance(bal *int64) { *bal = *bal - 10 } // แก้ไขค่าบน RAM โดยตรง
// make([]T, len, cap)
txs := make([]int64, 0, 1000) // จอง Underlying Array รองรับได้ 1,000 ตัวล่วงหน้า
// append ข้อมูล (ถ้าเต็มจะขยายความจุ X2 หรือ +25%)
txs = append(txs, 101, 102, 103)
// copy เพื่อตัดขาด Underlying Array ไม่ให้แชร์หน่วยความจำ
sub := make([]int64, len(txs))
copy(sub, txs) // คัดลอกไบต์แยกลง RAM ก้อนใหม่
// สร้าง map พร้อมจอง Hint Capacity
ledger := make(map[string]int64, 5000)
ledger["ACC-101"] = 15000
// การอ่านค่าแบบ Comma-ok Idiom (แยกแยะระหว่างค่า 0 จริงๆ กับไม่มีคีย์)
if balance, exists := ledger["ACC-101"]; exists {
fmt.Println("Balance:", balance)
}
// การทำ Set ขนาด 0-Byte Value เพื่อประหยัด RAM สูงสุด
uniqueAccounts := make(map[string]struct{})
uniqueAccounts["ACC-101"] = struct{}{} // struct{}{} กินพื้นที่ 0 ไบต์!
// จัดเรียงฟิลด์แบบ Descending Alignment (ใหญ่ -> กลาง -> เล็ก)
type TransactionRecord struct {
AccountID int64 // 8 Bytes (Offset 0)
Amount int64 // 8 Bytes (Offset 8)
Timestamp int32 // 4 Bytes (Offset 16)
Status uint8 // 1 Byte (Offset 20)
IsValid bool // 1 Byte (Offset 21)
// Trailing Padding 2 Bytes รวมเป็น 24 Bytes พอดี!
}
// ตรวจสอบขนาดด้วย unsafe package
fmt.Println("Size:", unsafe.Sizeof(TransactionRecord{})) // 24
fmt.Println("Align:", unsafe.Alignof(TransactionRecord{})) // 8
// ใช้ strings.Builder พร้อม Grow ล่วงหน้าเพื่อตัด Heap Allocation
var b strings.Builder
b.Grow(128)
b.WriteString("TX-STATUS: SUCCESS")
result := b.String()
// Zero-copy แปลง []byte -> string (Go 1.20+)
func FastBytesToString(buf []byte) string {
return unsafe.String(unsafe.SliceData(buf), len(buf))
}
ชุดข้อสอบมาตรฐาน 50 ข้อ ครอบคลุมเนื้อหา Day 1 ถึง Day 5 ทุกประเด็นทางสถาปัตยกรรม คลิกเลือกคำตอบเพื่อรับผลวิเคราะห์และคำอธิบายเชิงลึกทันที
ชุดปฏิบัติการเขียนโค้ดแก้ปัญหาระบบจริงในระบบ Core Banking Ledger และ Race Timing Stream ทุกข้อมีตัวตรวจ Linter และพิมพ์เขียว Blueprint
DeductBalance(currentBal *int64, fee int64) bool*currentBal >= fee ให้หักเงินบน RAM เดิมโดยตรงและคืนค่า truefalseCreateIsolatedBatch(stream []int64, count int) []int64count ตัวแรกจาก stream ออกมาmake และ copy เพื่อตัดขาด Underlying Array ไม่ให้เกิด Memory Sharing เด็ดขาดFilterUniqueAccounts(accIDs []string) map[string]struct{}make(map[string]struct{}, len(accIDs)) เพื่อจอง Bucket รองรับตั้งแต่แรกstruct{}{} เป็น Value เพื่อให้กินพื้นที่ 0 ไบต์LedgerHeader ด้านล่างมีขนาดบวม 40 ไบต์เพราะจัดเรียงฟิลด์ไม่ถูกต้องPacketToStringZeroCopy(packet []byte) stringunsafe.String ร่วมกับ unsafe.SliceData ในการแปลงข้อมูลโดยไม่เกิด Heap Allocationlen(packet) == 0 ให้คืนค่า "" อย่างปลอดภัย