3 điểm bởi GN⁺ 2024-03-18 | 1 bình luận | Chia sẻ qua WhatsApp
  • Khi Fedora và Ubuntu 24.04 LTS quay lại mặc định build kèm frame pointer cho các thành phần OS như libc, profiling trên Linux và Flame Graph có thể thấy stack đầy đủ hơn
  • Nếu không có frame pointer, profiler có thể dừng stack walking ở các lớp thư viện như libc hoặc đi theo frame sai, khiến CPU/Off-CPU Flame Graph bị hỏng
  • Thay đổi năm 2004 của gcc trên i386 là lựa chọn nhằm giành thêm một thanh ghi, nhưng khi lan sang cả x86-64, nó để lại chi phí dài hạn cho system profiler và khả năng quan sát dựa trên eBPF
  • Từ kinh nghiệm áp dụng Java và libc tại Netflix, chi phí thường dưới 1%; một số báo cáo ghi nhận 1~2%, còn trong các microbenchmark đặc thù hoặc workload bất thường có thể lên tới 10%
  • Dù có các lựa chọn thay thế như LBR, DWARF, eBPF stack walker, ORC, SFrames, Shadow Stacks, cách thực tế nhất để tìm cải thiện hiệu năng trong production hiện nay là bật frame pointer mặc định

Vì sao Flame Graph bị hỏng

  • CPU Flame Graph nhìn bề ngoài có thể có vẻ bình thường, nhưng trên hệ thống mà libc được biên dịch không có frame pointer, một số mẫu có thể bị gom sai phía trên [unknown]
    • Trong ví dụ, 15% mẫu ở bên trái nằm sai vị trí và thiếu các frame của ứng dụng
    • Profiler đi qua các frame kernel, tới syscall và libc syscall wrapper, rồi thất bại khi phân giải symbol của frame tiếp theo
  • Nguyên nhân là do tối ưu hóa của compiler khiến thanh ghi frame pointer được dùng để lưu dữ liệu thay vì làm mốc tham chiếu cho stack frame
    • Profiler không thể biết giá trị đó chỉ là một con số, nên thử diễn giải nó như địa chỉ hàm
    • Nếu giá trị không trỏ tới frame kế tiếp, stack walking sẽ dừng lại
    • Nếu tình cờ trông giống một pointer hợp lệ, có thể sinh ra junk frame sai
    • Nếu giá trị trỏ về chính nó, có thể tạo ra một tháp junk frame cho tới khi chạm giới hạn frame tối đa của perf
  • Off-CPU Flame Graph có thể xuất hiện nhiều hàm libc read/write và mutex, nên khi thiếu frame pointer sẽ bị hỏng nặng hơn
  • Nếu bản thân ứng dụng cũng được build không có frame pointer, không chỉ thư viện mà toàn bộ stack trace đều trở nên thiếu ổn định

Frame pointer cung cấp thông tin gì

  • ABI x86-64 định nghĩa %rbp có thể được dùng làm base pointer của stack frame, tức frame pointer
  • Các profiler/debugger bên ngoài như Linux perf và eBPF dùng thông tin này để đi qua stack trace và trực quan hóa kết quả thành Flame Graph
  • ABI x86-64 có chú thích rằng việc dùng %rbp là tùy chọn
    • Nếu dùng %rsp để đánh chỉ mục stack frame, có thể giảm hai lệnh trong prologue và epilogue
    • Có thể dùng %rbp như một thanh ghi đa dụng bổ sung

Việc loại bỏ năm 2004 và hệ quả 20 năm

  • Năm 2004, gcc được thay đổi ở i386 backend để mặc định hành xử tương đương -fomit-frame-pointer -ffixed-ebp
  • i386 chỉ có 4 thanh ghi đa dụng, nên giải phóng %ebp làm tăng đáng kể số thanh ghi có thể dùng
    • Lý do thay đổi cũng bao gồm mong muốn đạt hiệu năng tốt hơn compiler icc của Intel
    • Khi đó cũng có đánh giá rằng debugger sẽ không bị hỏng vì hỗ trợ các kỹ thuật stack walking khác
  • Thay đổi này cũng được áp dụng cho x86-64
    • x86-64 có từ 12 thanh ghi trở lên, nên lợi ích từ việc có thêm một thanh ghi không lớn như trên i386
    • Các system profiler ngày nay như eBPF chưa tồn tại vào thời điểm đó, và thay đổi này có thể khiến chúng bị hỏng
  • Năm 2004, Eric Schrock cho rằng trên amd64, lợi ích từ việc có thanh ghi đa dụng thứ 17 không đủ lớn so với mất mát về khả năng debug
    • Ông để lại nhận xét rằng nếu bắt đầu biên dịch /usr/bin không có frame pointer thì mọi thứ sẽ vượt khỏi tầm kiểm soát
    • Trên Linux, xu hướng tương tự lan rộng không chỉ ở /usr/bin mà cả /usr/lib và mã ứng dụng

Kinh nghiệm áp dụng với Java, libc và Netflix

  • Khi gia nhập Netflix năm 2014, do Java thiếu hỗ trợ frame pointer nên toàn bộ stack ứng dụng đều bị hỏng
  • Một bản sửa cho JVM c2 compiler đã được tạo, và Oracle làm lại nó rồi thêm vào JDK8u60 dưới dạng tùy chọn -XX:+PreserveFramePointer
  • Thay đổi ở Java giúp tìm ra nhiều cải thiện hiệu năng trong mã ứng dụng, nhưng libc vẫn làm hỏng một phần mẫu CPU và phần lớn Off-CPU Flame Graph
  • Sau đó, Netflix tự biên dịch libc có kèm frame pointer để dùng trong production, đồng thời cũng làm việc với Canonical về libc build sẵn cho Ubuntu
    • Trong một thời gian, việc dùng libc6-prof được khuyến nghị
    • libc6-proflibc6 có kèm frame pointer

Overhead hiệu năng và các trường hợp ngoại lệ

  • Trong quá trình áp dụng production, chi phí khi thêm frame pointer vào libc và Java thường dưới 1%
  • Có một ứng dụng ngoại lệ ghi nhận overhead 10%
    • Đó là một ứng dụng đặc biệt tạo stack trace hơn 1000 frame thông qua Groovy
    • Linux perf cũng không xử lý được trường hợp này, nên Arnaldo Carvalho de Melo của Red Hat đã thêm sysctl kernel.perf_event_max_stack cho workload của Netflix
    • Môi trường đó là máy ảo không có tính năng profiling phần cứng cấp thấp, nên không thể xác nhận toàn bộ 10% có phải do frame pointer hay không
  • Các báo cáo khác cũng ghi nhận chi phí khoảng 1% hoặc 2%
  • Microbenchmark có thể xấu đi tới 10%
    • Khi lặp lại các hàm nhỏ trong vòng lặp, các lệnh bổ sung có thể ảnh hưởng tới độ ấm của L1 cache hoặc ranh giới cache line
    • Trong trường hợp này, không hẳn do bản thân frame pointer; thêm bất cứ thứ gì vào hàm hot cũng có thể gây hiệu ứng tương tự
  • Benchmark Python scimark_sparse_mat_mult cũng có thể đạt tới 10%
    • Trong phân tích của Andrii Nakryiko, đây là một trường hợp đặc thù: với hàm lớn, gcc dùng offset tương đối theo %rbp thay vì offset %rsp, cần nhiều byte hơn và gây vấn đề hiệu năng
    • Sau đó cũng có tin rằng Python đã được sửa để có thể bật lại frame pointer mặc định
  • Những cải thiện hiệu năng tìm được nhờ frame pointer dao động từ 5% đến 500%, nên chi phí thường dưới 1% được xem là có thể biện minh
  • Nếu là thiết bị không có khả năng profiling hay debugging thì có thể biên dịch không có frame pointer, nhưng đối tượng chính ở đây là enterprise Linux và backend server

Fedora và Ubuntu bật mặc định

  • Các công ty lớn như Meta, Google, Netflix đã tự dùng libc có kèm frame pointer để bảo đảm khả năng profiling
  • Nỗ lực đầu tiên nhằm upstream việc bật mặc định vào Fedora đã dẫn tới tranh luận kéo dài
    • Thảo luận Fedora trở thành một thread với 116 bài viết
    • Một người tham gia yêu cầu Meta hoặc Netflix cung cấp hạ tầng side repository để kiểm thử, benchmark và đo kích thước mã
    • Jonathan Corbet đã tóm tắt việc này trong Fedora's tempest in a stack frame
  • Sau đó Fedora xem xét lại và chấp nhận đề xuất, trở thành bản phân phối đầu tiên bật lại frame pointer
  • Ubuntu cũng công bố frame pointers by default trong Ubuntu 24.04 LTS
  • Có thêm tin rằng Arch Linux cũng đang tiến hành bật frame pointer
  • Stack walking trong thư viện OS sẽ được cải thiện nhờ thay đổi này, nhưng runtime ứng dụng có thể cần cấu hình riêng
    • Java cung cấp tùy chọn -XX:+PreserveFramePointer
    • Go đã mặc định hỗ trợ frame pointer từ vài năm trước

Các ứng viên stack walking sau frame pointer

  • LBR(Last Branch Record): tính năng phần cứng của Intel, bị giới hạn 16 hoặc 32 frame nên không đủ cho phần lớn stack ứng dụng, nhưng có thể dùng như phương án cuối để lấy một phần thông tin stack
  • BTS(Branch Trace Store): tính năng của Intel, ít bị giới hạn độ sâu stack hơn nhưng có chi phí cho memory load/store và xử lý BTS buffer overflow interrupt
  • AET(Architectural Event Trace): tracer dựa trên JTAG, có thể trace CPU cấp thấp, BIOS và device event, và có vẻ cũng dùng được cho stack trace, nhưng chưa có kinh nghiệm sử dụng trực tiếp
  • DWARF: cơ chế binary debuginfo đã được dùng lâu trong debugger
    • Có ý kiến chỉ ra rằng từng có công việc JIT-to-DWARF trong các JIT runtime
    • Không kỳ vọng nó thực tế với Java JVM nơi c2 chạy liên tục trên server production bận rộn
    • Bản thân DWARF stack walking cũng có chi phí cao
  • eBPF stack walking: tracer bên ngoài có thể đi vào bên trong runtime như JVM mà không cần hỗ trợ từ runtime
    • Có thể phải đọc nhiều phần bên trong runtime bằng user space read, nên overhead có thể cao
    • Vì dễ bị ảnh hưởng bởi thay đổi của runtime, phù hợp hơn khi được phát hành và duy trì cùng code base của ngôn ngữ
  • ORC(oops rewind capability): stack unwinder nhẹ của Linux kernel, cho phép kernel mới vẫn duy trì stack walking dù loại bỏ frame pointer
  • SFrames(Stack Frames): phương thức unwinding stack người dùng nhẹ dựa trên ORC
  • Shadow Stacks: tính năng bảo mật của Intel và AMD, push địa chỉ return của hàm vào stack phần cứng riêng để kiểm chứng tại thời điểm return, và có vẻ cũng có thể tận dụng cho stack trace

Điều thay đổi ngay lúc này

  • Lợi ích hiệu năng trên i386, đánh giá tương thích debugger thời đó và cạnh tranh với icc — các lý do của việc bỏ frame pointer năm 2004 — không còn phù hợp với bối cảnh năm 2024
  • Việc loại bỏ frame pointer áp dụng cho x86-64 ngay từ lúc đó đã được đánh giá là lợi ích không đủ lớn so với mất mát về khả năng debug
  • Khi Fedora và Ubuntu khôi phục frame pointer, người dùng các bản phát hành năm 2024 sẽ có CPU Flame Graph dễ hiểu hơn và lần đầu có thể sử dụng đúng nghĩa Off-CPU Flame Graph
  • Continuous profiler cũng dễ thu được profile đầy đủ hơn mà không cần yêu cầu khách hàng thay đổi OS
  • Trong tương lai, có thể lại stack trace không cần frame pointer bằng các cách như SFrames hoặc Shadow Stacks, nhưng cải thiện khả thi hiện nay là bật frame pointer làm mặc định

1 bình luận

 
GN⁺ 2024-03-18
Các ý kiến trên Hacker News
  • Tôi nhớ thời điểm đầu những năm 2000, khi việc bỏ qua con trỏ khung ngăn xếp bắt đầu lan rộng
    Khi đó tôi học khoa học máy tính tại một trường đại học ở một quốc gia thế giới thứ ba nghèo, và vì máy tính cũ kỹ, chậm chạp nên phần lớn bài tập dùng trình biên dịch thay vì trình thông dịch
    Có nhiều môn thú vị, từ cấu trúc dữ liệu mức thấp, trình biên dịch, các routine số học bằng assembly cho tới driver thiết bị Minix; mỗi khi chương trình chạy kỳ lạ, tôi gắn gdb vào và tự lần theo stack ở mức assembly để debug
    Nhưng rồi đột nhiên -fomit-frame-pointer trở nên thịnh hành, khiến stack trace bắt đầu trở nên vô nghĩa, và việc debug segfault hay illegal instruction khó hơn nhiều
    Cuối cùng, để tránh các phiên debug bị hỏng, tôi bắt đầu dùng Python cho hầu như mọi việc; hiệu năng mất một đến hai bậc độ lớn, nhưng việc học Python về sau lại hữu ích

    • Tôi tò mò không biết bạn có biết -fno-omit-frame-pointer không
  • Rất vui khi Fedora được nhắc tới. Việc duy trì trạng thái bật frame pointer trên toàn bộ bản phân phối là một cuộc chiến khá mệt mỏi
    Ví dụ: https://pagure.io/fesco/issue/3084
    Vẫn còn một huyền thoại rằng overhead của frame pointer là lớn, xuất phát từ một trường hợp Python từng chậm hơn +10%, nhưng nay đã được sửa
    Overhead đo được thực tế là dưới 1%, trong khi lợi ích thu được ở một số ứng dụng cụ thể lớn hơn nhiều

    • Nhận định “overhead đo được thực tế là dưới 1%” có vẻ phóng đại. Overhead phụ thuộc rất nhiều vào workload được đo, nên khó áp dụng một cách phổ quát
      Nó cũng không khớp lắm với các phép đo phía kernel Linux, nơi tôi thấy con số nằm trong khoảng 5–10%: https://lore.kernel.org/lkml/20170602104048.jkkzssljsompjdwy...
      Với nhiều workload khác nhau như netperf, microbenchmark của page allocator, pgbench, sqlite, việc bật frame pointer gây overhead 5–10%, và điểm quan trọng là tác động đã được thấy với PostgreSQL và SQLite
      DBMS là một trong những cách tốt để tạo áp lực mạnh lên hệ thống
    • Để dùng flame graph đúng cách trong OCaml 5, hoặc phải dùng frame pointer, hoặc dùng LBR vốn có độ sâu hạn chế và có thể không hoạt động tùy CPU, hoặc phải thay đổi khá lớn phía perf/eBPF để xử lý hai stack của OCaml
      OCaml 5 dùng stack riêng cho mã OCaml và mã C; GDB có thể nối hai stack này bằng thông tin DWARF, nhưng call graph DWARF của perf thì không làm được: https://github.com/ocaml/ocaml/issues/12563#issuecomment-193...
      Nếu cần lý do để tiếp tục giữ frame pointer trong các bản phát hành tương lai, có thể lấy OCaml 5 làm ví dụ
      Giờ tôi mới biết Fedora 39 đã bật frame pointer theo mặc định; còn công việc profiling thường ngày của tôi vẫn chủ yếu diễn ra trên các hệ thống kiểu CentOS 7, nơi tôi vẫn dùng perf record --call-graph dwarf -F 47 -a
    • Trên 32-bit, frame pointer vẫn gây khó xử, nên không phù hợp với các lĩnh vực như IoT ngày nay
      Lý do loại bỏ frame pointer không phải là huyền thoại, mà bắt nguồn từ thực tế của thời kỳ trước 64-bit, và chuyện đó cũng chưa quá xa xưa
      Ngay cả hiện nay, nếu muốn thổi sức sống mới vào các hệ thống 64-bit cũ, những tối ưu hóa như vậy vẫn có ý nghĩa
      Lý tưởng là nó cũng nên là mặc định trong các hệ thống coi trọng bảo mật, và không phải mọi thứ đều cần được tối ưu cho “khả năng quan sát”
    • Tôi cho rằng các overhead “dưới 1%” tích tụ lại khiến cảm giác sử dụng máy tính chậm hơn một cách đo được so với 30 năm trước. Những overhead nhỏ như vậy cuối cùng sẽ cộng dồn
  • Có một điều Apple làm tốt trên ARM: họ bảo đảm frame pointer x29 luôn trỏ tới một bản ghi frame hợp lệ
    Một số hàm như leaf function hoặc tail call có thể không tạo mục trong danh sách, nhưng nhờ đó, ngay cả khi không có thông tin debug, stack trace vẫn luôn còn có ý nghĩa
    https://developer.apple.com/documentation/xcode/writing-arm6...

    • Trên các nền tảng Apple thường có những vấn đề khác về khả năng diễn giải. Ứng dụng Objective-C / Swift có nhiều block và closure lồng sâu, nên backtrace thường bị phân tán qua nhiều thread, và tôi vẫn chưa biết giải pháp tốt cho việc này
  • Năm 2005, tôi đứng ở phía phản đối tại Google, và suy nghĩ khi đó rất đơn giản
    Ngay cả nếu $BIG_COMPANY quyết định biên dịch mọi thứ với frame pointer, phần còn lại của cộng đồng cũng sẽ không làm vậy, nên chúng tôi sẽ tiếp tục sa vào một cuộc tranh luận không thể thắng với một cộng đồng lớn hơn nhiều
    Kết quả là cuộc tranh luận đó gần như đã trở thành một cuộc tranh luận kéo dài 20 năm, và về sau tôi viết các bản vá để libunwind hoạt động trong gperftools, rồi duy trì libunwind trong vài năm
    Giờ tôi đã chuyển sang một lĩnh vực điện toán khác và chỉ còn là người quan sát thụ động, nhưng đọc lại lịch sử từ góc nhìn của phía đối lập thật thú vị

    • Tôi tò mò việc thêm frame pointer sẽ cản trở như thế nào. Tôi không rõ về mặt chức năng thì sẽ phát sinh vấn đề gì
  • Nếu định truyền RBP làm frame pointer thì thà để hai stack cũng được
    Một stack do RBP trỏ tới để lưu activation frame, stack còn lại do RSP trỏ tới và chỉ lưu địa chỉ trả về
    Khi đó call stack đúng nghĩa là một mảng phẳng các địa chỉ trả về, nên cũng không cần phải “đi qua stack” nữa
    Ngay từ đầu tôi cũng không hiểu vì sao lại lưu địa chỉ trả về gần biến cục bộ, và thấy nhược điểm quá nhiều

    • Vì như vậy việc quản lý bộ nhớ đơn giản hơn. Stack frame là một bump pointer đơn giản hầu như luôn có trong cache, và khi tràn chỉ cần một guard page
      Cách đề xuất cần hai guard page, thao tác stack cũng tăng gấp đôi, và khả năng cache miss cũng tăng gấp đôi
    • Tính năng CPU được bài viết nhắc thoáng qua, shadow stack, khá giống với điều này. Tuy nhiên mục đích chính của nó là bảo mật
    • Tôi cũng thắc mắc vì sao stack lại tăng theo “hướng sai”, khiến các chương trình hoạt động sai gây ra vấn đề bảo mật
      Tôi biết lý do, nhưng như nhiều thứ khác, có lẽ lần cuối nó còn hợp lý là khoảng 30 năm trước, và ảnh hưởng của nó thì khá thú vị
    • Có vẻ bạn đã sẵn sàng thích Forth rồi ;-)
      Kỳ lạ là bài Wikipedia dường như không nhấn mạnh như một đặc trưng lớn của mô hình rằng Forth có thể truy cập cả parameter stack lẫn return stack
      https://en.wikipedia.org/wiki/Forth_(programming_language)
    • Lợi ích của việc lưu địa chỉ trả về ở nơi khác là không rõ ràng, trừ khi phần cứng hỗ trợ thứ như shadow stack
      Cần chứng minh rằng chi phí chuyển nó sang trang riêng và quản lý hai con trỏ thực sự rẻ hơn stack cookie/protector, thứ vốn chỉ cung cấp bảo vệ ở những nơi cần thiết
      Nó cũng không có lợi ích bảo mật thực chất hơn stack protector hiện nay. Nếu có thể đọc/ghi tùy ý thì cuối cùng vẫn có thể dẫn đến việc vượt qua tính toàn vẹn luồng điều khiển
  • Virgil không dùng frame pointer. Nếu không có cấp phát stack động, kích thước frame của một hàm cụ thể là cố định và có thể tìm bằng một lần tra bảng nhị phân đơn giản
    Kỹ thuật của Virgil dùng thêm phạm vi dựa trên chỉ mục trang để thu hẹp việc tra cứu xuống chỉ còn vài phép so sánh trung bình, kết hợp thông tin unwind với stackmap cho GC, và dùng rất ít không gian
    Mã chính nằm ở https://github.com/titzer/virgil/blob/master/rt/native/Nativ... và phần mã còn lại trong cùng thư mục triển khai giải mã metadata
    Tôi cho rằng frame pointer chỉ có ý nghĩa khi kích thước frame là động, tức là khi cấp phát dữ liệu trên stack
    Dùng một cơ chế động trong khi cơ chế tĩnh đã đủ là điều kỳ lạ, và có vẻ chủ yếu là vì không có sự thống nhất về ABI mã hóa metadata hoặc routine unwind
    Con số đo được 1–2% là đáng tin, và ở mức tương tự chi phí kiểm tra biên mảng
    Việc dành đối xử đặc biệt cho chi phí 1% trong debugging và profiling, nhưng lại ngần ngại khi thêm lớp bảo mật, cho thấy thứ tự ưu tiên rất kỳ lạ

    • Có thể thêm kiểm tra biên cho C, nhưng chi phí sẽ lớn hơn 1–2% rất nhiều
      std::vector của C++ mặc định tắt kiểm tra biên, mà theo tôi là vì C++ được thiết kế bởi những người hoàn toàn điên rồ, và cũng dành cho những người như vậy
      Ngoài ra tôi không nghĩ ngay ra ngôn ngữ nào không có kiểm tra biên
  • Bài viết hay. Tôi đã thấy tiếc khi frame pointer biến mất
    Không chỉ trên các hệ thống khác, mà ngay cả trên Linux, nhiều người đã khổ sở trong thời gian dài vì thiếu frame pointer, và đã cố giữ nó ở càng nhiều môi trường càng tốt
    Thấy Linux chính thống đưa nó trở lại đem lại cảm giác được công nhận, nhưng đồng thời cũng hơi bực bội

    • Tôi thật sự tò mò. Tôi biết unwind stack bằng DWARF là phiền phức, nhưng không hiểu vì sao nó tệ đến mức đáng làm chậm toàn bộ mã hệ thống
      Trên họ Debian thì nó chậm vì lý do giấy phép khiến perf chỉ đóng gói đường unwind chậm, nhưng nếu công cụ ổn thì gần như không cảm nhận được khác biệt
      Tôi muốn biết mình đang bỏ lỡ điều gì
  • Nhìn chung tôi ủng hộ frame pointer, nhưng sau vài năm làm việc trong lĩnh vực này, tôi có một số cảm nhận
    Nhiều cách unwind stack dựa trên frame pointer không tính đến một vấn đề mà thông tin unwind DWARF không gặp phải. Việc thiết lập frame không mang tính nguyên tử, mà gồm hai lệnh push $rbpmov $rsp $rbp; nếu snapshot được chụp trong lúc push, có thể bỏ lỡ frame cha
    Có thể giảm nhẹ bằng cách kiểm tra mã, nhưng cũng có thể có push %rbp không liên quan đến stack frame, nên trông gần giống heuristic hơn
    Chúng tôi cũng đã phát triển một giải pháp unwind nhanh trong kernel dựa trên BPF mà Brendan đã nhắc đến: https://web.archive.org/web/20231222054207/https://www.polar...
    Cách này không dùng nguyên DWARF CFI, mà chuyển đổi nó sang một định dạng truy cập ngẫu nhiên có thể dùng trong BPF
    Hiện tại chỉ hỗ trợ các section JIT có frame pointer, nhưng tôi nghĩ có thể triển khai việc unwind interpreter của JVM sao cho khớp với unwind native
    Lý tưởng nhất là nên bật frame pointer theo từng trường hợp, và benchmarking là yếu tố then chốt
    Tùy theo ngành và tính chất phần mềm, sự đánh đổi giữa hiệu năng, khả năng quan sát và các chỉ số kinh doanh có thể khác nhau rất lớn
    Phía Fedora đã làm việc này rất xuất sắc và nghiêm ngặt
    Ngoài ra, một build system có thể thay đổi thiết lập này trên toàn hệ thống, bao gồm cả các thư viện phụ thuộc, sẽ rất hữu ích không chỉ cho kiểm thử mà cả khi áp dụng vào vận hành
    Cuối cùng, tôi rất kỳ vọng vào SFrame mà Indu đang làm. Nó có vẻ sẽ giải quyết được nhiều vấn đề hiện tại, đồng thời cho phép người dùng chọn có dùng frame pointer hay không, nhưng có thể sẽ mất vài năm để hạ tầng sẵn sàng và mọi người nâng cấp

    • Để có flame graph tốt, cần bật frame pointer trên toàn bộ bản phân phối Linux
      Để hiểu thực sự chuyện gì đang diễn ra, cần phân tích toàn hệ thống, và với cấu trúc các bản phân phối Linux nhị phân hiện nay như Fedora hay Debian, gần như không có lựa chọn thay thế nào khác
    • Cũng có thể làm bằng một lệnh: ENTER N,0 dành ra N dung lượng stack cho biến cục bộ, đại khái tương đương PUSH EBP, MOV ESP,ESP, SUB SP,N
      Tuy nhiên tôi không nhớ ENTER có trong x86-64 hay không
      Dù vậy, giữa CALL và thiết lập frame vẫn không nguyên tử, nên nếu snapshot được chụp sau CALL nhưng trước ENTER, sẽ không lấy được thiết lập frame
      Lý do ENTER ít được dùng là vì nó bị đánh giá là quá chậm
      LEAVE được dùng vì nó nhanh bằng hoặc nhanh hơn chuỗi lệnh mà nó thay thế, nhưng ENTER thì toán hạng thứ hai làm hỏng hiệu năng
      Toán hạng đó dùng để hàm lồng nhau truy cập stack frame phía trên, và chi phí sử dụng rất lớn
    • Thiết lập frame không nguyên tử chắc chắn là vấn đề với CPU profiler, nhưng không phải vấn đề với allocation profiling, Off-CPU profiling, hay các kiểu profiling khác không dựa trên interrupt
    • Phía unwind JVM cũng đang có tiến triển tốt
  • Thật thú vị khi giờ mới hiểu vì sao trong profile lại xuất hiện cả một “núi” [unknown]
    Tuy nhiên việc biện minh cho điều này không dễ. Chênh lệch hiệu năng 2% thực ra là khá lớn
    Sẽ tốt hơn nếu có thể kiểm soát việc có đưa frame pointer vào hay không một cách tinh hơn
    Nếu có profiling chi tiết, ta có thể quyết định liệu một hàm hoặc một đơn vị biên dịch cụ thể có cần frame pointer hay không
    Tôi cũng sẽ không ngạc nhiên nếu kết quả cho thấy chỉ một số ít tác vụ chậm đi đáng kể khi thêm frame pointer, còn phần còn lại hầu như không bị ảnh hưởng

    • Chênh lệch hiệu năng 2% không lớn đến thế. Đặc biệt là nếu nhờ profiling mà tìm được bottleneck và đạt cải thiện trên 10% thì càng không đáng kể
    • Trong GCC, có thể gắn thuộc tính vào khai báo hàm để bật/tắt theo từng hàm. Tuy nhiên trong LLVM thì không hoạt động
      __attribute__((optimize("no-omit-frame-pointer")))
      __attribute__((optimize("omit-frame-pointer")))
    • Trong ứng dụng thực tế, chi phí hiệu năng có thể nhỏ hơn 2% rất nhiều
      Những benchmark kiểu này hơi nhân tạo nên không nên tin hoàn toàn, và ứng dụng thực tế thường cho kết quả rất khác
      Profiling rất quan trọng; tôi từng profile mã một cách kỹ lưỡng và làm nhiều đoạn chạy nhanh hơn tới 20%
      Nếu ứng dụng cực kỳ nhạy với mất mát hiệu năng, có thể bật frame pointer và profile trong phòng thí nghiệm, rồi bỏ qua nó ở bản phát hành cho khách hàng
    • Overhead đo được thấp hơn 1% một chút. Trước đây từng có một số trường hợp hiếm khiến hiệu năng giảm mạnh vì frame pointer, nhưng nay đã được sửa
    • Thông thường nhỏ hơn 2% rất nhiều
  • Hỗ trợ mã JIT đáng tiếc là chưa tốt, nhưng LLVM có hook tuyệt vời để ghi lại từng method được sinh ra cùng địa chỉ của nó
    Vì vậy có thể tạo một cơ chế unwind stack chế độ hỗn hợp đơn giản tương đối dễ, nhưng chủ yếu là khả thi từ bên trong process
    Dòng DNN của Intel có vẻ dump thông tin ra một file công khai mà perf có thể đọc, nhưng bản thân kernel của oneDNN cứ tái sử dụng RBP liên tục nên trên thực tế gần như vô dụng
    Khẳng định trong bài rằng “các runtime JIT như Java JVM không có thông tin DWARF” cũng khiến tôi ngạc nhiên
    Tôi thắc mắc liệu nó bị tắt mặc định, hay đúng nghĩa là không thể dùng được
    Tìm kiếm thì thường dẫn đến nội dung muốn đưa cả phía JNI/C vào stack JVM: https://github.com/async-profiler/async-profiler/issues/215