Giải pháp tối ưu hiệu năng và công cụ thực tiễn trong WeChat

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

Thẻ: C++ performance optimization memory management Lock-Free Data Structures Cache Optimization

Đăng vào ngày 26 tháng 5 lúc 01:36