Lập trình viên lãng phí nhiều tuần để tối ưu hóa hiệu suất gấp 4 lần nhưng không mang lại lợi ích thực tế do dữ liệu benchmark sai lệch

Nhóm Cộng đồng BigGo
Lập trình viên lãng phí nhiều tuần để tối ưu hóa hiệu suất gấp 4 lần nhưng không mang lại lợi ích thực tế do dữ liệu benchmark sai lệch

Câu chuyện của một kỹ sư phần mềm về việc tối ưu hóa đi sai hướng đã gây ra cuộc thảo luận rộng rãi về tầm quan trọng của dữ liệu benchmark thực tế trong kiểm thử hiệu suất. Lập trình viên này đã dành hai tuần để tạo ra một implementation assembly được tối ưu hóa thủ công, mang lại tốc độ nhanh hơn 4 lần trong quá trình kiểm thử, nhưng sau đó phát hiện ra nó không mang lại lợi ích gì trong môi trường production do các giả định benchmark hoàn toàn sai lệch.

Cạm bẫy của dữ liệu ngẫu nhiên trong kiểm thử hiệu suất

Vấn đề cốt lõi xuất phát từ việc sử dụng các số ngẫu nhiên để kiểm thử tối ưu hóa mã hóa Varint. Mặc dù cách tiếp cận này có vẻ hợp lý cho việc kiểm thử toàn diện, nhưng nó tạo ra một kịch bản hoàn toàn không thực tế. Các số 64-bit ngẫu nhiên về mặt toán học có xu hướng thiên về các giá trị cực lớn - với 50% yêu cầu tối đa 10 byte để mã hóa và gần như tất cả các số khác cần 8-9 byte. Điều này có nghĩa là benchmark về cơ bản đang kiểm thử kịch bản hiệu suất tệ nhất của thuật toán thay vì các mẫu sử dụng thông thường.

Cộng đồng đã nhấn mạnh rằng điều này đại diện cho một thách thức rộng lớn hơn trong công việc tối ưu hóa hiệu suất. Việc tạo ra các kịch bản kiểm thử đại diện và triển khai chúng một cách chính xác trong microbenchmark đều là những nhiệm vụ cực kỳ khó khăn, và những thất bại trong lĩnh vực này có thể khó phát hiện cho đến khi đã đầu tư một lượng thời gian đáng kể.

Varint (Variable-length integer): Một phương pháp mã hóa compact sử dụng 1-10 byte để biểu diễn các số nguyên, với các số nhỏ hơn yêu cầu ít byte hơn

Phân bố Kích thước Mã hóa Varint cho Số 64-bit Ngẫu nhiên:

  • 50% yêu cầu 10 byte (tối đa)
  • ~49.6% yêu cầu 9 byte
  • ~0.38% yêu cầu 8 byte
  • ~0.003% yêu cầu 7 byte
  • ~0.00000000000009% yêu cầu 2 byte
  • ~0.0000000000000006% yêu cầu 1 byte

Phân phối dữ liệu thực tế kể một câu chuyện khác

Sự thật được hé lộ khi kiểm tra dữ liệu production thực tế, cho thấy rằng các số trong thế giới thực có xu hướng nhỏ hơn đáng kể so với những gì dữ liệu kiểm thử ngẫu nhiên gợi ý. Nhìn xung quanh các con số hàng ngày - số lượng trang, số tham chiếu, mã sản phẩm - hầu hết đều vừa vặn trong chỉ hai byte. Sự khác biệt cơ bản này giải thích tại sao mã hóa Varint tồn tại ngay từ đầu: để xử lý hiệu quả các số nhỏ chiếm ưu thế trong các ứng dụng thực tế.

Hiện tượng này kết nối với các nguyên lý toán học rộng lớn hơn như Định luật Benford, quan sát thấy rằng trong nhiều tập dữ liệu thực tế, các chữ số đầu nhỏ hơn xuất hiện thường xuyên hơn các chữ số lớn hơn. Sự không khớp giữa tính ngẫu nhiên toán học và phân phối dữ liệu thực tế đại diện cho một cạm bẫy phổ biến đối với các lập trình viên tối ưu hóa thuật toán.

Định dạng mã hóa Varint:

  • Số < 128: 1nnnnnnn (1 byte)
  • Số < 16,384: 1nnnnnnn 0nnnnnnn (2 byte)
  • Số < 2,097,152: 1nnnnnnn 1nnnnnnn 0nnnnnnn (3 byte)
  • Số nguyên 32-bit: cần 1-5 byte
  • Số nguyên 64-bit: cần 1-10 byte
  • Được sử dụng trong: Google Protobuf , Apache Thrift , WASM , DWARF

Thách thức của việc benchmark đại diện

Cuộc thảo luận đã tiết lộ rằng ngay cả các lập trình viên có kinh nghiệm cũng gặp khó khăn trong việc tạo ra các bài kiểm tra hiệu suất có ý nghĩa. Một số tổ chức đã khám phá việc sử dụng hồ sơ dữ liệu production hoặc áp dụng bộ lọc cho dữ liệu người dùng thực để có benchmark chính xác hơn, mặc dù những cách tiếp cận này đối mặt với những thách thức thực tế riêng.

Việc xác định một kịch bản sử dụng đại diện để tối ưu hóa và sau đó triển khai kịch bản đó trong một driver kiểm thử microbenchmark đều cực kỳ khó để thực hiện đúng

Câu chuyện này phục vụ như một lời nhắc nhở rằng những con số benchmark ấn tượng không có ý nghĩa gì nếu không có điều kiện kiểm thử thực tế. Mặc dù thành tựu kỹ thuật của lập trình viên trong việc tạo ra thuật toán nhanh hơn 4 lần là thực sự, nhưng việc tối ưu hóa đã giải quyết một vấn đề không tồn tại trong thực tế. Cuối cùng, trải nghiệm này trở nên có giá trị như một proof-of-concept cho các tối ưu hóa JIT tùy chỉnh, mặc dù implementation cụ thể đã được rollback.

Trường hợp này nhấn mạnh tại sao tối ưu hóa hiệu suất thành công đòi hỏi sự chú ý đến phương pháp đo lường nhiều như đối với các cải tiến mã thực tế. Không có dữ liệu đại diện, ngay cả những tối ưu hóa tinh vi nhất cũng có thể trở thành những giải pháp phức tạp cho các vấn đề không tồn tại.

Tham khảo: That time I wasted weeks hand optimizing assembly because I benchmarked on random data