01 Sử dụng lớp thay thế giao thức ProtoBuf
Trong các API được gọi nhiều lần, nên tránh sử dụng giao thức ProtoBuf và thay thế bằng lớp C++. Lý do là ProtoBuf sử dụng chiến lược phân bổ bộ nhớ Arena, gây phức tạp hơn so với quản lý bộ nhớ của lớp C++. Khi có lượng lớn phân bổ/giải phóng bộ nhớ, hiệu năng của ProtoBuf có thể kém hơn lớp C++ tới 3 lần do hiện tượng phân mảnh bộ nhớ.
// Ví dụ lớp thay thế
class ThongTinKetQua {
public:
void CapNhatThongSo(const std::string& ten, const std::string& giaTri) {
tenThamSo_ = ten;
giaTriThamSo_ = giaTri;
}
private:
std::string tenThamSo_;
std::string giaTriThamSo_;
};
02 Tối ưu cấu trúc dữ liệu theo hiệu quả cache
Mảng có thể nhanh hơn bảng băm trong một số trường hợp nhờ tính liên tục của dữ liệu trong cache. Ví dụ dưới đây sử dụng vector thay thế unordered_map để tăng tốc độ truy xuất:
struct ThongTinSns {
uint32_t maSns;
uint32_t giaTri;
};
class Context {
private:
std::vector<ThongTinSns> danhSachSns_;
};
03 Sử dụng jemalloc/tcmalloc thay thế malloc mặc định
Cấu hình jemalloc trong hệ thống giúp giảm 20% thời gian xử lý nhờ:
- Giảm phân mảnh bộ nhớ
- Nâng cao tỷ lệ trúng cache
- Tăng hiệu năng đa luồng
cc_library(
name = "he_thong_api",
srcs = ["api_impl.cc"],
deps = ["//third_party:jemalloc"],
copts = ["-O3", "-std=c++17"]
)
04 Cấu trúc dữ liệu không khóa (lock-free)
Trong hệ thống có 2.6 tỷ/s lượt gọi API, giải pháp buffer kép giúp:
- Không cần khóa khi đọc/ghi
- Giảm xung đột đa luồng
- Tăng gấp 3 lần hiệu năng
struct HeThongChiaSe {
void* boNho[2];
volatile int trangThai; // 0: trống, 1: đang ghi, 2: đang đọc
void ChuyenDoiBuffer() {
trangThai = (trangThai == 1) ? 2 : 1;
}
};
05 Tối ưu theo ngữ cảnh cụ thể
Ví dụ tối ưu cho kịch bản đánh dấu người dùng:
// Trước tối ưu
struct ThamSoThucNghiem {
int idThucNghiem;
int nhom;
std::string thamSo;
};
// Sau tối ưu
struct ThongTinDanhDau {
int idThucNghiem;
int nhom;
uint64_t khoiNgoac;
};
06 Công cụ phân tích hiệu năng
- perf: Công cụ phân tích hiệu năng Linux
- FlameGraph: Công cụ tạo biểu đồ lửa
- Godbolt: Công cụ xem mã assembly
07 Kết luận
Tối ưu hiệu năng là quá trình liên tục. Cần cân nhắc giữa hiệu quả cải tiến và chi phí phát triển, đồng thời duy trì tính bảo trì của mã nguồn. Một số hướng tối ưu khác như:
- Giảm sao chép đối tượng lớn
- Tách biệt I/O và tính toán
- Tối ưu dự đoán nhánh