Phần 37 kết luận strace làm chậm 446 lần. Bài này đo công cụ ở đầu kia của thang: perf.

perf: độ chính xác và chi phí

Kiểm tra bằng chương trình đã biết đáp án

Chương trình có bốn hàm, mỗi hàm chạy một vòng lặp có số vòng khác nhau. Tỷ lệ CPU thật tính được từ mã nguồn:

Hàm Số vòng lặp Tỷ lệ thật perf báo
rat_dat 100.000.000 62,50% 62,65%
dat 40.000.000 25,00% 24,89%
vua 15.000.000 9,38% 9,31%
re 5.000.000 3,13% 3,16%

Sai lệch lớn nhất: 0,15 điểm phần trăm.

perf không đoán và không mô phỏng. Nó ngắt chương trình vài trăm tới vài nghìn lần mỗi giây, mỗi lần ghi lại con trỏ lệnh đang ở đâu, rồi đếm. Đủ mẫu thì phân bố mẫu hội tụ về phân bố thời gian thật.

Và nó gần như miễn phí

Cách chạy Thời gian Số mẫu
Không perf 3,99 s
perf record -F 99 3,97 s 392
perf record -F 999 3,96 s 3.957
perf record -F 9999 3,95 s ~3.000
perf record -F 49999 3,90 s ~3.000

Không đo được chênh lệch nào. Các lần chạy có perf thậm chí nhỉnh hơn — đó là nhiễu.

So với phần trước: strace làm chậm 446 lần, perf record làm chậm không đo được.

Lý do khác nhau ở bản chất. strace dừng tiến trình ở mỗi lời gọi hệ thống. perf để tiến trình chạy tự do và chỉ thỉnh thoảng ngắt ra ghi một con số.

Một bẫy nhỏ trong bảng trên

-F 9999-F 49999 đều cho khoảng 3.000 mẫu — bằng -F 999. Nhân đã hạ tần số xuống mà không báo gì:

cat /proc/sys/kernel/perf_event_max_sample_rate     # 100000 tren may toi do

Đặt -F cao hơn giới hạn này thì perf chạy ở tần số bị hạ, và bạn nghĩ mình đang lấy mẫu dày hơn thực tế. Kiểm tra bằng cách chia số mẫu cho thời gian chạy.

Với phần lớn việc, -F 99 là đủ. 392 mẫu đã đủ để xếp hạng bốn hàm; sai số chỉ ảnh hưởng tới những hàm chiếm dưới 1%.

Quy trình ba bước

# 1. Thu thap
perf record -F 99 -g --call-graph dwarf -p <pid> -- sleep 30

# 2. Xem xep hang phang
perf report --stdio --sort symbol --percent-limit 1

# 3. Xem theo ngan xep goi
perf report --stdio -g graph,0.5,caller

-g bật ghi ngăn xếp lời gọi — thứ trả lời câu hỏi "hàm này bị gọi từ đâu", thường quan trọng hơn "hàm nào tốn nhất".

Ba cách lấy ngăn xếp, và lựa chọn này quan trọng:

Cách Yêu cầu Ghi chú
--call-graph fp biên dịch với -fno-omit-frame-pointer rẻ nhất, chính xác
--call-graph dwarf có thông tin gỡ lỗi chạy được với binary phát hành, tốn hơn
--call-graph lbr CPU Intel đời mới rất rẻ, giới hạn độ sâu

Nhiều bản phân phối biên dịch với -fomit-frame-pointer, và khi đó fp cho ngăn xếp cụt. Nếu perf report chỉ hiện một tầng, đó là nguyên nhân.

Biểu đồ ngọn lửa

perf report dạng văn bản khó đọc với ngăn xếp sâu. Biểu đồ ngọn lửa giải quyết việc đó:

perf record -F 99 -g -p <pid> -- sleep 30
perf script | stackcollapse-perf.pl | flamegraph.pl > ho-so.svg

Trục ngang là tỷ lệ thời gian, không phải thời gian trôi qua. Chiều rộng của một khối là phần CPU nó chiếm; chiều cao là độ sâu ngăn xếp. Khối rộng ở gần đỉnh là chỗ đáng tối ưu.

perf không chỉ đo CPU

perf list | head -30                          # moi su kien do duoc
perf stat -e cache-misses,cache-references ./ct
perf stat -e branch-misses,branches ./ct
perf stat -e context-switches,cpu-migrations ./ct
perf stat -e 'syscalls:sys_enter_*' -p <pid> -- sleep 5

Tỷ lệ trượt bộ nhớ đệm là con số nối thẳng với phần 9: nếu cache-misses / cache-references cao, vấn đề nằm ở bố cục dữ liệu chứ không ở thuật toán.

perf stat không cần lấy mẫu — nó đọc bộ đếm phần cứng, nên chi phí gần bằng 0 và con số là chính xác, không phải ước lượng.

Chạy perf trong container

cat /proc/sys/kernel/perf_event_paranoid
Giá trị Cho phép
3 không gì
2 chỉ sự kiện mức người dùng của tiến trình mình
1 thêm sự kiện nhân
0 thêm bộ đếm cấp CPU
−1 tất cả

Máy tôi đo có giá trị 2, và tôi phải chạy container --privileged để thu thập. Trong Kubernetes:

securityContext:
  capabilities:
    add: ["SYS_ADMIN", "PERFMON"]

PERFMON (từ nhân 5.8) là lựa chọn hẹp hơn SYS_ADMIN và nên dùng khi có.

Chỗ perf không thấy được

Thời gian chờ. perf record mặc định lấy mẫu theo chu kỳ CPU, nên tiến trình đang ngủ chờ I/O hoặc chờ khoá không xuất hiện. Một dịch vụ tốn 90% thời gian chờ cơ sở dữ liệu sẽ cho hồ sơ trông rất sạch.

Muốn thấy phần chờ, dùng sự kiện khác:

perf record -e sched:sched_switch -g -p <pid> -- sleep 10     # ho so ngoai CPU

Mã sinh lúc chạy. JVM, V8, .NET biên dịch mã ngay lúc chạy nên perf chỉ thấy địa chỉ, không thấy tên hàm. Cần tệp ánh xạ:

java -XX:+PreserveFramePointer -agentpath:/path/libperfmap.so ...
node --perf-basic-prof app.js

Thử ba mươi giây

Lập hồ sơ một tiến trình đang chạy:

p=<pid>
perf record -F 99 -g -o /tmp/p.data -p $p -- sleep 20
perf report -i /tmp/p.data --stdio --sort symbol --percent-limit 1 2>/dev/null | head -20

echo "--- kiem tra so mau co dung tan so khong ---"
perf report -i /tmp/p.data --stdio 2>/dev/null | grep -E '^# (Samples|Event count)'

Chia số mẫu cho 20 giây. Nếu nó thấp hơn nhiều so với 99, tiến trình phần lớn thời gian không chạy trên CPU — và lúc đó vấn đề của bạn không phải CPU, mà là chờ. Đó cũng là kết luận có giá trị, chỉ là nó nằm ở chỗ hồ sơ CPU không nhìn tới.

Phần sau: bẫy khi đo — những sai lầm tôi đã mắc trong ba mươi tám phần trước.