เรียน Go ภาษาง่าย 📘

⚡

เจาะลึก Performance: เร็วขึ้นโดยไม่ต้องเก่ง ฉบับภาษาง่าย

bench, pprof, escape, Pool, PGO

8 บท
12 ข้อสอบ
1. ของที่เร็วขึ้นเองใน 1.26-1.27
  • Green Tea GC (1.26 เปิด default): แม่บ้านเก็บขยะรุ่นใหม่ หยุดเสิร์ฟ (pause) สั้นลง โปรแกรมค้างน้อยลงโดยไม่ตั้งอะไร
  • slice บน stack บ่อยขึ้น: compiler เก่งขึ้น กินแรมน้อยลงฟรีๆ
  • cgo เร็ว ~30%: ใครเรียก C จาก Go เร็วขึ้นเฉยๆ ถ้าไม่ใช้ C ให้ CGO_ENABLED=0 ไปเลยเล็กสุด
  • encoding/json เดิมเร็วขึ้น: เพราะเอาไส้ json/v2 มาใส่ให้แล้ว ไม่ต้องแก้โค้ดก็เร็วขึ้น
bash
GODEBUG=gctrace=1 go run . # ดู GC pause ว่ายาวไหม
✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


2. วัดด้วย Bench + Pprof flame
go
func BenchmarkSq(b *testing.B) {
    for i := 0; i < b.N; i++ { _ = sq(gen(1,2,3)) }
}
bash
go test -bench=. -benchmem
go tool pprof -http=:8080 cpu.out

อย่าเดา ให้ดูกราฟก่อนแก้ 90% ของความช้ามาจาก 10% ของโค้ด

✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


3. Escape: ของหนีไปโกดัง

Stack = ลิ้นชักเร็ว, Heap = โกดังช้าต้องให้ GC เก็บ Go เลือกให้เอง (escape analysis):

bash
go build -gcflags='-m' ./... # หาบรรทัด escapes to heap

ทริกบ้านๆ ลดหนี:

  • ส่งค่าก้อนเล็กเป็น value ไม่ต้อง pointer ทุกอัน
  • อย่าคืน pointer ของของที่สร้างใน func ถ้าไม่จำเป็น
  • ใช้ sync.Pool reuse ของแพง (buffer) แทนสร้างทิ้ง
✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


4. Inline PGO SIMD
  • Inline: func เล็กๆ compiler ก็อปวางมาเลยไม่ต้องโทร 1.26 มี //go:fix inline ให้ go fix ช่วยไล่
  • PGO: เอาผลวิ่งจริงมาบอก compiler ว่าทางไหนคนเดินเยอะ
bash
go build -pgo=cpu.pprof -o app .
  • SIMD simd/archsimd (ทดลอง 1.26): สั่ง CPU ทำหลายเลขพร้อมกัน เหมาะสายภาพ/เสียง/AI รู้ชื่อไว้พอ
✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


5. JSON + HTTP เร็วขึ้น
  • JSON: เขียน Marshal/Unmarshal เดิมได้เลย 1.27 เร็วขึ้นเอง อยากซิ่งค่อยไป json/v2 (ตั้งค่าได้) + jsontext (สตรีมไฟล์ยักษ์)
  • HTTP: ใช้ net/http ธรรมดา netpoller ช่วยรอเน็ตโดยไม่กิน thread อยู่แล้ว อย่าเขียน loop รอเอง
✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


6. เช็กลิสต์จูน
  1. อัป 1.27.1 + go mod tidy แล้ววัดใหม่ (อาจเร็วขึ้นเฉยๆ)
  2. go test -bench + pprof flame หาแท่งกว้างสุด
  3. -gcflags='-m' ดู escape แปลกๆ
  4. -race ต้องผ่านก่อนจูน ไม่งั้นยิ่งแก้ยิ่งพัง
  5. ค่อยคิดเรื่อง Pool/PGO/SIMD ทีหลัง อย่าพุ่งไปท่ายากก่อน

อ้างอิง: Go 1.26 (Green Tea, cgo -30%, stack alloc, pprof flame default, simd), Go 1.27 (json/v2)

✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


ภาคผนวก: ตัวอย่างเพิ่ม + Best Practices
ตัวอย่างเพิ่ม 1: benchmark + ดู allocs
go
func BenchmarkJoin(b *testing.B) {
    b.ReportAllocs()
    for i := 0; i < b.N; i++ {
        _ = strings.Join(words, ",")
    }
}
bash
go test -bench=. -benchmem ./...
ตัวอย่างเพิ่ม 2: strings.Builder แทน += ใน loop
go
var sb strings.Builder
sb.Grow(1024) // จองก่อนถ้ารู้ขนาดคร่าวๆ
for _, w := range words {
    sb.WriteString(w)
}
s := sb.String()
ตัวอย่างเพิ่ม 3: sync.Pool ใช้ของแพงซ้ำ
go
var bufPool = sync.Pool{
    New: func() any { return new(bytes.Buffer) },
}

buf := bufPool.Get().(*bytes.Buffer)
buf.Reset()
defer bufPool.Put(buf) // คืนถัง ใช้ซ้ำไม่ต้อง new บ่อย
Best practices เช็กลิสต์
  • วัดก่อนเดาเสมอ (bench + pprof) อย่าเดาว่าช้าตรงไหน
  • ดู -benchmem (allocs) ก่อนดูเวลา alloc เยอะ = GC เหนื่อย
  • อัป Go ก่อนจูน (1.26-1.27 เร็วขึ้นเองหลายจุด)
  • เลือก algorithm/IO ดีๆ ก่อน ค่อยลงท่ายาก (Pool/PGO/SIMD)
  • strings.Builder แทน += ใน loop, Grow ถ้ารู้ขนาด
  • Pool ใช้กับของแพงๆ (buffer) อย่าใช้ทุกอย่าง (โค้ดรกเปล่าๆ)
แบบฝึกหัด
  • bench += vs Builder ดูว่า allocs ต่างกันกี่เท่า
  • เปิด flame graph หาแท่งกว้างสุดในโค้ดตัวเอง