Skip to content

การวิเคราะห์ประสิทธิภาพ

เมื่อโปรแกรมหนึ่งเขียนเสร็จแล้ว ความต้องการของเราที่มีต่อมันไม่ใช่เพียงแค่สามารถรันได้เท่านั้น แต่ยังหวังว่ามันจะเป็นแอปพลิเคชันที่มีเสถียรภาพและมีประสิทธิภาพสูง ผ่านการทดสอบต่างๆ เราสามารถรับประกันเสถียรภาพส่วนใหญ่ของโปรแกรมได้ ส่วนโปรแกรมจะมีประสิทธิภาพสูงหรือไม่ จำเป็นต้องเราทำการวิเคราะห์ประสิทธิภาพ ในเนื้อหาก่อนหน้านี้ วิธีการเดียวสำหรับการวิเคราะห์ประสิทธิภาพคือการทดสอบผ่าน Benchmark เพื่อวัดเวลาการทำงานเฉลี่ย การจัดสรรหน่วยความจำ ฯลฯ ของฟังก์ชันหนึ่งๆ อย่างไรก็ตาม ความต้องการในการวิเคราะห์ประสิทธิภาพของโปรแกรมในความเป็นจริงมีมากกว่านั้นมาก บางครั้งเราต้องการวิเคราะห์การใช้ CPU โดยรวมของโปรแกรม การใช้หน่วยความจำ สถานการณ์การจัดสรร heap สถานะ goroutine เส้นทางการทำงานของโค้ดที่เป็น hot spot ฯลฯ ซึ่งสิ่งเหล่านี้ Benchmark ไม่สามารถตอบสนองได้ โชคดีที่เครื่องมือ go ได้รวมเครื่องมือวิเคราะห์ประสิทธิภาพมากมายไว้ให้ผู้พัฒนาได้ใช้ ต่อไปจะอธิบายทีละข้อ

การวิเคราะห์การหนี (Escape Analysis)

ใน go การจัดสรรหน่วยความจำของตัวแปรถูกกำหนดโดยคอมไพเลอร์ โดยทั่วไปจะจัดสรรบน stack และ heap สองที่นี้ หากตัวแปรที่ควรจัดสรรบน stack ถูกจัดสรรบน heap สถานการณ์นี้เรียกว่าการหนี (escape) การวิเคราะห์การหนีคือการวิเคราะห์สถานการณ์การจัดสรรหน่วยความจำในโปรแกรม เนื่องจากมันดำเนินการในช่วงคอมไพล์ จึงเป็นการวิเคราะห์แบบสถิตย์ (static analysis) ชนิดหนึ่ง

TIP

ไปที่บทความ การจัดสรรหน่วยความจำ เพื่อเรียนรู้ว่า go จัดสรรหน่วยความจำอย่างไร

การอ้างอิงพอยน์เตอร์ท้องถิ่น

go
package main

func main() {
  GetPerson()
}

type Person struct {
  Name string
  Mom  *Person
}

func GetPerson() Person {
  mom := Person{Name: "lili"}
  son := Person{Name: "jack", Mom: &mom}
  return son
}

ในฟังก์ชัน GetPerson ได้สร้างตัวแปร mom เนื่องจากมันถูกสร้างในฟังก์ชัน เดิมควรจัดสรรบน stack แต่เนื่องจากมันถูกอ้างอิงโดยฟิลด์ Mom ของ son และ son ถูกส่งคืนเป็นค่าส่งกลับของฟังก์ชัน ดังนั้นคอมไพเลอร์จึงจัดสรรมันบน heap นี่เป็นตัวอย่างที่ง่ายมาก ดังนั้นการเข้าใจจึงไม่ต้องใช้แรงมาก แต่หากเป็นโปรเจกต์ที่ใหญ่กว่า มีจำนวนบรรทัดโค้ดหลายหมื่นบรรทัด การวิเคราะห์ด้วยมนุษย์ก็ไม่ใช่เรื่องง่าย ดังนั้นจึงจำเป็นต้องใช้เครื่องมือทำการวิเคราะห์การหนี ดังที่กล่าวไว้ก่อนหน้านี้ การจัดสรรหน่วยความจำถูกควบคุมโดยคอมไพเลอร์ ดังนั้นการวิเคราะห์การหนีก็ดำเนินการโดยคอมไพเลอร์เช่นกัน การใช้งานง่ายมาก เพียงดำเนินการคำสั่งต่อไปนี้:

bash
$ go build -gcflags="-m -m -l"

gcflags คือพารามิเตอร์ของคอมไพเลอร์ gc

  • -m พิมพ์คำแนะนำการปรับปรุงโค้ด หากปรากฏสองตัวจะแสดงรายละเอียดมากขึ้น
  • -l ปิดการใช้งานการปรับปรุงแบบ inline

ผลลัพธ์เป็นดังนี้

bash
$ go build -gcflags="-m -m -l" .
# golearn/example
./main.go:13:2: mom escapes to heap:
./main.go:13:2:   flow: son = &mom:
./main.go:13:2:     from &mom (address-of) at ./main.go:14:35
./main.go:13:2:     from Person{...} (struct literal element) at ./main.go:14:15
./main.go:13:2:     from son := Person{...} (assign) at ./main.go:14:6
./main.go:13:2:   flow: ~r0 = son:
./main.go:13:2:     from return son (return) at ./main.go:15:2
./main.go:13:2: moved to heap: mom

คอมไพเลอร์บอกเราอย่างชัดเจนว่าตัวแปร mom เกิดการหนี สาเหตุคือค่าส่งกลับมีพอยน์เตอร์ท้องถิ่นในฟังก์ชัน นอกจากสถานการณ์นี้แล้ว ยังมีสถานการณ์อื่นๆ ที่อาจเกิดการหนีได้

::: tips

หากคุณสนใจรายละเอียดของการวิเคราะห์การหนี สามารถดูเนื้อหาเพิ่มเติมได้ในไลบรารีมาตรฐาน cmd/compile/internal/escape/escape.go

:::

การอ้างอิงโดย closure

หาก closure อ้างอิงตัวแปรภายนอกฟังก์ชัน ตัวแปรนั้นก็จะหนีไปยัง heap ด้วย ซึ่งเข้าใจได้ง่าย

go
package main

func main() {
  a := make([]string, 0)
  do(func() []string {
    return a
  })
}

func do(f func() []string) []string {
  return f()
}

ผลลัพธ์

$ go build -gcflags="-m -m -l" .
# golearn/example
./main.go:10:9: f does not escape
./main.go:4:2: main capturing by value: a (addr=false assign=false width=24)
./main.go:4:11: make([]string, 0) escapes to heap:
./main.go:4:11:   flow: a = &{storage for make([]string, 0)}:
./main.go:4:11:     from make([]string, 0) (spill) at ./main.go:4:11
./main.go:4:11:     from a := make([]string, 0) (assign) at ./main.go:4:4
./main.go:4:11:   flow: ~r0 = a:
./main.go:4:11:     from return a (return) at ./main.go:6:3
./main.go:4:11: make([]string, 0) escapes to heap
./main.go:5:5: func literal does not escape

พื้นที่ไม่เพียงพอ

เมื่อพื้นที่ stack ไม่เพียงพอ ก็会发生การหนีเช่นกัน สไลซ์ที่สร้างด้านล่างขอความจุ 1<<15

go
package main

func main() {
  _ = make([]int, 0, 1<<15)
}

ผลลัพธ์

$ go build -gcflags="-m -m -l" .
# golearn/example
./main.go:4:10: make([]int, 0, 32768) escapes to heap:
./main.go:4:10:   flow: {heap} = &{storage for make([]int, 0, 32768)}:
./main.go:4:10:     from make([]int, 0, 32768) (too large for stack) at ./main.go:4:10
./main.go:4:10: make([]int, 0, 32768) escapes to heap

ความยาวไม่ทราบค่า

เมื่อความยาวของสไลซ์เป็นตัวแปร เนื่องจากความยาวไม่ทราบค่า จึง会发生การหนี (map ไม่เป็นเช่นนั้น)

go
package main

func main() {
  n := 100
  _ = make([]int, n)
}

ผลลัพธ์

$ go build -gcflags="-m -m -l" .
# golearn/example
./main.go:5:10: make([]int, n) escapes to heap:
./main.go:5:10:   flow: {heap} = &{storage for make([]int, n)}:
./main.go:5:10:     from make([]int, n) (non-constant size) at ./main.go:5:10
./main.go:5:10: make([]int, n) escapes to heap

ยังมีสถานการณ์พิเศษอีกอย่างหนึ่งคือเมื่อพารามิเตอร์ฟังก์ชันเป็นประเภท ...any ก็อาจเกิดการหนีได้

go
package main

import "fmt"

func main() {
  n := 100
  fmt.Println(n)
}

ผลลัพธ์

$ go build -gcflags="-m -m -l" .
# golearn/example
./main.go:7:14: n escapes to heap:
./main.go:7:14:   flow: {storage for ... argument} = &{storage for n}:
./main.go:7:14:     from n (spill) at ./main.go:7:14
./main.go:7:14:     from ... argument (slice-literal-element) at ./main.go:7:13
./main.go:7:14:   flow: {heap} = {storage for ... argument}:
./main.go:7:14:     from ... argument (spill) at ./main.go:7:13
./main.go:7:14:     from fmt.Println(... argument...) (call parameter) at ./main.go:7:13
./main.go:7:13: ... argument does not escape
./main.go:7:14: n escapes to heap

เหตุผลที่เราต้องทำการวิเคราะห์การหนี ควบคุมการจัดสรรหน่วยความจำอย่างละเอียดเช่นนี้ ส่วนใหญ่เพื่อลดภาระของ GC แต่ go ไม่ใช่ภาษา c อำนาจตัดสินใจสุดท้ายในการจัดสรรหน่วยความจำยังคงอยู่ในมือของคอมไพเลอร์ ยกเว้นในกรณีที่ต้องการประสิทธิภาพสูงมาก ส่วนใหญ่เราไม่จำเป็นต้องใส่ใจรายละเอียดการจัดสรรหน่วยความจำมากเกินไป เพราะจุดประสงค์ของการเกิด GC ก็เพื่อปลดปล่อยผู้พัฒนา

รายละเอียดเล็กน้อย

สำหรับประเภทอ้างอิงบางประเภท เมื่อยืนยันแล้วว่าไม่ใช้มันอีกแล้ว เราสามารถตั้งค่าเป็น nil เพื่อบอก GC ว่าสามารถคืนหน่วยความจำมันได้

go
type Writer struct {
  buf []byte
}

func (w Writer) Close() error {
  w.buff = nil
  return nil
}

pprof

pprof (program profiling) เป็นเครื่องมือวิเคราะห์ประสิทธิภาพของโปรแกรมที่ทรงพลัง มันจะทำการสุ่มตัวอย่างข้อมูลขณะรันไทม์ของโปรแกรม ครอบคลุม cpu หน่วยความจำ goroutine lock ข้อมูล stack ฯลฯ จากนั้นใช้เครื่องมือวิเคราะห์และแสดงผลข้อมูลที่สุ่มตัวอย่าง

ดังนั้นขั้นตอนการใช้ pprof มีเพียงสองขั้นตอน:

  1. เก็บข้อมูล
  2. วิเคราะห์ผลลัพธ์

การเก็บข้อมูล

วิธีการเก็บข้อมูลมีสองวิธี คือ อัตโนมัติและแมนนวล (แต่ละวิธีมีข้อดีและข้อเสีย) ก่อนหน้านี้ ให้เขียนฟังก์ชันง่ายๆ เพื่อจำลองการใช้หน่วยความจำและ cpu

go
func Do() {
  for i := 0; i < 10; i++ {
    slice := makeSlice()
    sortSlice(slice)
  }
}

func makeSlice() []int {
  var s []int
  for range 1 << 24 {
    s = append(s, rand.Int())
  }
  return s
}

func sortSlice(s []int) {
  slices.Sort(s)
}

แมนนวล

การเก็บข้อมูลแบบแมนนวลคือการควบคุมผ่านโค้ด ข้อดีคือสามารถควบคุมได้ ยืดหยุ่น สามารถกำหนดเองได้ การใช้ pprof ในโค้ดโดยตรงจำเป็นต้องนำเข้าแพ็กเกจ runtime/pprof

go
package main

import (
  "log"
  "os"
  "runtime/pprof"
)

func main() {
    Do()
  w, _ := os.Create("heap.pb")
  heapProfile := pprof.Lookup("heap")
  err := heapProfile.WriteTo(w, 0)
  if err != nil {
    log.Fatal(err)
  }
}

พารามิเตอร์ที่รองรับโดย pprof.Lookup มีดังนี้

go
profiles.m = map[string]*Profile{
    "goroutine":    goroutineProfile,
    "threadcreate": threadcreateProfile,
    "heap":         heapProfile,
    "allocs":       allocsProfile,
    "block":        blockProfile,
    "mutex":        mutexProfile,
}

ฟังก์ชันนี้จะเขียนข้อมูลที่เก็บได้ลงในไฟล์ที่ระบุ ตัวเลขที่ส่งเข้าไปขณะเขียนมีความหมายดังนี้

  • 0 เขียนข้อมูล Protobuf ที่บีบอัดแล้ว ไม่มีความสามารถในการอ่าน
  • 1 เขียนข้อมูลรูปแบบข้อความ สามารถอ่านได้ http interface ส่งคืนข้อมูลประเภทนี้
  • 2 ใช้ได้เฉพาะ goroutine แสดงการพิมพ์ข้อมูล stack สไตล์ panic

การเก็บข้อมูล cpu ต้องใช้ฟังก์ชัน pprof.StartCPUProfile แยกต่างหาก ต้องใช้เวลาหนึ่งในการสุ่มตัวอย่าง และข้อมูลดิบไม่สามารถอ่านได้ ดังนี้

go
package main

import (
  "log"
  "os"
  "runtime/pprof"
  "time"
)

func main() {
    Do()
  w, _ := os.Create("cpu.out")
  err := pprof.StartCPUProfile(w)
  if err != nil {
    log.Fatal(err)
  }
  time.Sleep(time.Second * 10)
  pprof.StopCPUProfile()
}

การเก็บข้อมูล trace ก็เช่นกัน

go
package main

import (
  "log"
  "os"
  "runtime/trace"
  "time"
)

func main() {
    Do()
  w, _ := os.Create("trace.out")
  err := trace.Start(w)
  if err != nil {
    log.Fatal(err)
  }
  time.Sleep(time.Second * 10)
  trace.Stop()
}

อัตโนมัติ

แพ็กเกจ net/http/pprof ได้ห่อหุ้มฟังก์ชันวิเคราะห์ข้างต้นเป็น http interface และลงทะเบียนใน route เริ่มต้น ดังนี้

go
package pprof

import ...

func init() {
    http.HandleFunc("/debug/pprof/", Index)
    http.HandleFunc("/debug/pprof/cmdline", Cmdline)
    http.HandleFunc("/debug/pprof/profile", Profile)
    http.HandleFunc("/debug/pprof/symbol", Symbol)
    http.HandleFunc("/debug/pprof/trace", Trace)
}

这使得ทำให้เราสามารถรันการเก็บข้อมูล pprof ได้ด้วยคลิกเดียว

go
package main

import (
  "net/http"
    // อย่าลืมนำเข้าแพ็กเกจนี้
  _ "net/http/pprof"
)

func main() {
    go func(){
        http.ListenAndServe(":8080", nil)
    }
    for {
        Do()
    }
}

จากนั้นเปิดเบราว์เซอร์访问 http://127.0.0.1:8080/debug/pprof จะปรากฏหน้าจอดังนี้

ในหน้ามีตัวเลือกให้เลือกหลายตัว ซึ่งแทนด้วย

  • allocs: การสุ่มตัวอย่างการจัดสรรหน่วยความจำ
  • block: การติดตามการบล็อกของ synchronization primitives
  • cmdline: การเรียก command line ของโปรแกรมปัจจุบัน
  • goroutine: การติดตาม goroutine ทั้งหมด
  • heap: การสุ่มตัวอย่างการจัดสรรหน่วยความจำสำหรับ对象ที่ยัง存活
  • mutex: การติดตามข้อมูลเกี่ยวกับ mutex
  • profile: การวิเคราะห์ cpu จะวิเคราะห์ช่วงเวลาหนึ่งและดาวน์โหลดไฟล์
  • threadcreate: การวิเคราะห์สาเหตุที่ทำให้สร้าง OS thread ใหม่
  • trace: การติดตามสถานการณ์การดำเนินการของโปรแกรมปัจจุบัน จะดาวน์โหลดไฟล์เช่นกัน

ข้อมูลที่นี่ส่วนใหญ่มีความสามารถในการอ่านไม่สูง ส่วนใหญ่ใช้สำหรับให้เครื่องมือวิเคราะห์ ดังรูปต่อไปนี้

งานวิเคราะห์เฉพาะต้องเก็บไว้ทำภายหลัง นอกจากตัวเลือก profile และ trace สองตัวแล้ว หากคุณต้องการดาวน์โหลดไฟล์ข้อมูลในเว็บเพจ สามารถลบพารามิเตอร์ query debug=1 ออกได้ นอกจากนี้ยังสามารถรวม interfaces เหล่านี้เข้ากับ route ของคุณเองแทนที่จะใช้ route เริ่มต้น ดังนี้

go
package main

import (
  "net/http"
  "net/http/pprof"
)

func main() {
  mux := http.NewServeMux()
  mux.HandleFunc("/trace", pprof.Trace)
  servre := &http.Server{
    Addr:    ":8080",
    Handler: mux,
  }
  servre.ListenAndServe()
}

ด้วยวิธีนี้ ยังสามารถรวมเข้ากับ web framework อื่นๆ ได้ เช่น gin, iris เป็นต้น

การวิเคราะห์

หลังจากได้ไฟล์ข้อมูลที่เก็บแล้ว มีสองวิธีในการวิเคราะห์ คือ command line หรือเว็บเพจ ทั้งสองต้องใช้เครื่องมือ command line pprof go ได้รวมเครื่องมือนี้ไว้โดยค่าเริ่มต้น ดังนั้นไม่ต้องดาวน์โหลดเพิ่มเติม

ที่อยู่โอเพนซอร์สของ pprof: google/pprof: pprof is a tool for visualization and analysis of profiling data (github.com)

Command line

ใช้ไฟล์ข้อมูลที่เก็บไว้ก่อนหน้านี้เป็นพารามิเตอร์

bash
$ go tool pprof heap.pb

หากข้อมูลถูกเก็บโดยเว็บ ให้ใช้ web url แทนชื่อไฟล์

bash
$ go tool pprof -http :8080 http://127.0.0.1/debug/pprof/heap

จากนั้นจะปรากฏ command line แบบ interactive

bash
15:27:38.3266862 +0800 CST
Type: inuse_space
Time: Apr 15, 2024 at 3:27pm (CST)
No samples were found with the default sample value type.
Try "sample_index" command to analyze different sample values.
Entering interactive mode (type "help" for commands, "o" for options)
(pprof)

พิมพ์ help สามารถดูคำสั่งอื่นๆ

  Commands:
    callgrind        Outputs a graph in callgrind format
    comments         Output all profile comments
    disasm           Output assembly listings annotated with samples
    dot              Outputs a graph in DOT format
    eog              Visualize graph through eog
    evince           Visualize graph through evince
  ...

ในการดูข้อมูลใน command line โดยทั่วไปใช้คำสั่ง top หรือใช้คำสั่ง traces ก็ได้ แต่ผลลัพธ์ยาวมาก คำสั่ง top ดูคร่าวๆ เท่านั้น

(pprof) top 5
Showing nodes accounting for 117.49MB, 100% of 117.49MB total
      flat  flat%   sum%        cum   cum%
  117.49MB   100%   100%   117.49MB   100%  main.makeSlice (inline)
         0     0%   100%   117.49MB   100%  main.Do
         0     0%   100%   117.49MB   100%  main.main
         0     0%   100%   117.49MB   100%  runtime.main

แนะนำตัวชี้วัดบางตัว (cpu เช่นกัน)

  • flat แทนทรัพยากรที่ฟังก์ชันปัจจุบันใช้
  • cum แทนผลรวมทรัพยากรที่ฟังก์ชันปัจจุบันและ call chain ต่อไปใช้
  • flat% flat/total
  • cum% cum/total

เราจะเห็นได้ชัดเจนว่าการใช้หน่วยความจำของ call chain ทั้งหมดคือ 117.49MB เนื่องจากฟังก์ชัน Do เองไม่ทำอะไรเลย เพียงเรียกฟังก์ชันอื่น ดังนั้นตัวชี้วัด flat ของมันคือ 0 การสร้างสไลซ์ถูกจัดการโดยฟังก์ชัน makeSlice ดังนั้นตัวชี้วัด flat ของมันคือ 100%

เราสามารถแปลงเป็นรูปแบบ visualization pprof รองรับรูปแบบมากมาย เช่น pdf, svg, png, gif ฯลฯ (ต้องติดตั้ง Graphviz)

(pprof) png
Generating report in profile001.png

ผ่านรูปภาพเราสามารถเห็นสถานการณ์การใช้หน่วยความจำของ call chain ทั้งหมดได้ชัดเจนขึ้น

ดูผ่านคำสั่ง list ในรูปแบบซอร์สโค้ด

(pprof) list Do
Total: 117.49MB
ROUTINE ======================== main.Do in D:\WorkSpace\Code\GoLeran\golearn\example\main.go
         0   117.49MB (flat, cum)   100% of Total
         .          .     21:func Do() {
         .          .     22:   for i := 0; i < 10; i++ {
         .   117.49MB     23:           slice := makeSlice()
         .          .     24:           sortSlice(slice)
         .          .     25:   }
         .          .     26:}
         .          .     27:
         .          .     28:func makeSlice() []int {

สำหรับรูปภาพและซอร์สโค้ด ยังสามารถใช้คำสั่ง web และ weblist เพื่อดูรูปภาพและซอร์สโค้ดในเบราว์เซอร์ได้

เว็บเพจ

ก่อนนี้เพื่อให้ความหลากหลายของข้อมูลมากขึ้น ให้แก้ไขฟังก์ชันจำลองเล็กน้อย

go
func Do1() {
  for i := 0; i < 10; i++ {
    slice := makeSlice()
    sortSlice(slice)
  }
}

func Do2() {
  for i := 0; i < 10; i++ {
    slice := makeSlice()
    sortSlice(slice)
  }
}

func makeSlice() []int {
  var s []int
  for range 1 << 12 {
    s = append(s, rand.Int())
  }
  return s
}

func sortSlice(s []int) {
  slices.Sort(s)
}

การวิเคราะห์ผ่านเว็บเพจสามารถแสดงผลแบบ visualization ไม่ต้องดำเนินการ command line ด้วยตนเอง เมื่อใช้การวิเคราะห์ผ่านเว็บเพจ เพียงดำเนินการคำสั่งต่อไปนี้

bash
$ go tool pprof -http :8080 heap.pb

หากข้อมูลถูกเก็บโดยเว็บ ให้ใช้ web url แทนชื่อไฟล์

bash
$ go tool pprof -http :8080 http://127.0.0.1:9090/debug/pprof/heap
$ go tool pprof -http :8080 http://127.0.0.1:9090/debug/pprof/profile
$ go tool pprof -http :8080 http://127.0.0.1:9090/debug/pprof/goroutine

TIP

เกี่ยวกับวิธีการวิเคราะห์ข้อมูล ไปที่ pprof: How to read the graph เพื่อเรียนรู้เพิ่มเติม

ในเว็บเพจมี 6 รายการที่สามารถดูได้

  • Top เหมือนคำสั่ง top
  • Graph กราฟเส้น
  • Flame Graph กราฟเปลวไฟ
  • Peek
  • Source ดูซอร์สโค้ด
  • Disassemble ดูการ disassemble

สำหรับหน่วยความจำสามารถวิเคราะห์ได้สี่มิติ

  • alloc_objects: จำนวน对象 ทั้งหมดที่จัดสรรแล้ว รวมถึงที่ปล่อยแล้ว
  • alloc_spcae: พื้นที่หน่วยความจำทั้งหมดที่จัดสรรแล้ว รวมถึงที่ปล่อยแล้ว
  • inuse_objects: จำนวน对象 ที่กำลังใช้
  • inuse_space: พื้นที่หน่วยความจำที่กำลังใช้

ภาพวิเคราะห์หน่วยความจำ

โหนดใบสีขาวด้านล่างสุดในภาพแทนการ占用ของ对象ขนาดต่างๆ

ภาพวิเคราะห์ cpu

เกี่ยวกับกราฟเส้น มี几点ที่ควรสังเกต

  • สีของบล็อกยิ่งเข้ม การ占用ยิ่งสูง เส้นยิ่งหนา การ占用ยิ่งสูง
  • เส้นทึบแทนการเรียกโดยตรง เส้นประแทนการข้าม call chain บางส่วน

กราฟเปลวไฟหน่วยความจำ

กราฟเปลวไฟ cpu

สำหรับกราฟเปลวไฟ มองจากบนลงล่างคือ call chain มองจากซ้ายไปขวาคือเปอร์เซ็นต์การ占用ของ cum

trace

pprof 主要负责การวิเคราะห์การใช้ทรัพยากรของโปรแกรม ส่วน trace เหมาะสมกว่าสำหรับการติดตามรายละเอียดการดำเนินการของโปรแกรม ไฟล์ข้อมูลของมันไม่เข้ากันกับ pprof งานวิเคราะห์ที่เกี่ยวข้องดำเนินการโดยคำสั่ง go tool trace

หากเป็นข้อมูลที่เก็บแบบแมนนวล สามารถใช้ชื่อไฟล์เป็นพารามิเตอร์

$ go tool trace trace.out

หากเป็นข้อมูลที่เก็บแบบอัตโนมัติ ก็เช่นกัน

bash
$ curl http://127.0.0.1:8080/debug/pprof/trace > trace.out && go tool trace trace.out

หลังจากดำเนินการจะเปิด web server

2024/04/15 17:15:40 Preparing trace for viewer...
2024/04/15 17:15:40 Splitting trace for viewer...
2024/04/15 17:15:40 Opening browser. Trace viewer is listening on http://127.0.0.1:51805

หลังจากเปิดหน้าประมาณดังนี้

这里面ประกอบด้วยส่วนต่อไปนี้ ข้อมูลเหล่านี้ค่อนข้างเข้าใจยาก

  • Event timelines for running goroutines

    • trace by proc: แสดง timeline ของ goroutine ที่ทำงานบน processor นั้นๆ ในแต่ละช่วงเวลา

    • trace by thread: แสดง timeline ของ goroutine ที่ทำงานบน OS thread ในแต่ละช่วงเวลา

    • Goroutine analysis: แสดงข้อมูลสถิติเกี่ยวกับ goroutine ของ每组 main function

  • Profiles

    • Network blocking profile: ข้อมูล goroutine ที่ถูกบล็อกเนื่องจาก network IO
    • Synchronization blocking profile: ข้อมูล goroutine ที่ถูกบล็อกเนื่องจาก synchronization primitives
    • Syscall profile: ข้อมูล goroutine ที่ถูกบล็อกเนื่องจาก system call
  • User-defined tasks and regions

    • User-defined tasks: ข้อมูล goroutine ของ tasks ที่ผู้ใช้กำหนด
    • User-defined regions: ข้อมูล goroutine ของ regions ของโค้ดที่ผู้ใช้กำหนด
  • Garbage collection metrics

    • Minimum mutator utilization: แสดงเวลาสูงสุดของ GC ล่าสุด

Golang by www.golangdev.cn edit