Tài liệu

Sử dụng BufferLease

Thời gian đọc 4 phút
Trên trang này

BufferLease là nền tảng quản lý bộ nhớ của Nalix. Nó cung cấp một lớp trừu tượng có đếm tham chiếu và có pool trên các mảng byte thô, giúp các pipeline mạng không cấp phát bộ nhớ (zero-allocation).


1. Khái niệm cốt lõi

Trong mạng hiệu năng cao, Garbage Collector (GC) là nút thắt chính. Việc cấp phát new byte[] cho mỗi gói tin đi ra tạo ra “nhiễu heap”, dẫn đến các lần GC tạm dừng.

Nalix giải quyết vấn đề này bằng Pooled Buffer Lease (BufferLease được hỗ trợ bởi BufferPoolManager và ArrayPool<byte>.Shared). Thay vì cấp phát bộ nhớ mới, bạn thuê một bộ đệm có sẵn, dùng nó, rồi trả lại pool. BufferLease tự động hóa vòng đời này bằng đếm tham chiếu nguyên tử và theo dõi rò rỉ tùy chọn (BufferOptions).

Cấu trúc phân cấp của buffer

Architecture Diagram
graph TD
    A[BufferLease] -->|Rents Shell| B[ObjectPoolManager]
    A -->|Rents Array| C[BufferPoolManager]
    C -->|Delegates| D["ArrayPool&lt;byte&gt;.Shared"]
    C -->|Optional Leak Tracking| E[BufferOptions Leak Tracker]

2. Tạo gói tin đi ra

Để gửi dữ liệu mà không cấp phát bộ nhớ, hãy theo mẫu Rent-Serialize-Commit-Send (Thuê - Tuần tự hóa - Xác nhận - Gửi).

Triển khai từng bước

public async ValueTask BroadcastUpdate(IConnection connection, GameUpdate packet)
{
    // 1. RENT: Lấy một lease từ pool.
    // Thao tác này là O(1) và không cấp phát trên heap.
    using var lease = BufferLease.Rent(packet.Length);

    // 2. SERIALIZE: Ghi trực tiếp vào toàn bộ dung lượng của lease.
    // lease.SpanFull cho bạn quyền truy cập vào toàn bộ mảng đã thuê.
    int written = packet.Serialize(lease.SpanFull);

    // 3. COMMIT: Cập nhật độ dài của lease cho khớp với dữ liệu thực đã ghi.
    // Điều này đảm bảo các thành phần phía sau (nén, mã hóa)
    // chỉ xử lý các byte hợp lệ.
    lease.CommitLength(written);

    // 4. SEND: Truyền ReadOnlyMemory<byte> cho transport.
    // Transport sẽ truyền dữ liệu đi. Khối 'using' đảm bảo
    // buffer được trả về pool ngay cả khi gửi thất bại.
    await connection.TCP.SendAsync(lease.Memory);
}

3. Vòng đời và đếm tham chiếu

BufferLease hỗ trợ đếm tham chiếu thông qua Retain() và Dispose().

Mẫu giữ tham chiếu

Nếu bạn cần truyền một lease cho tác vụ nền bất đồng bộ hoặc hàng đợi broadcast, bạn phải gọi Retain().

public void HandleIncoming(IBufferLease lease)
{
    // Chúng ta muốn xử lý lease này ở chế độ nền
    lease.Retain(); // Tăng số tham chiếu lên 2

    _ = Task.Run(() =>
    {
        try
        {
            ProcessLongRunning(lease.Memory);
        }
        finally
        {
            lease.Dispose(); // Giảm số tham chiếu
        }
    });

    // Lease gốc được framework giải phóng tại đây (số tham chiếu về 1)
}

4. Nâng cao: Chuyển quyền sở hữu

Trong một số trường hợp, bạn có thể muốn “tách” mảng bên dưới khỏi lease (ví dụ khi truyền nó cho một API cũ tự quản lý bộ nhớ).

if (lease.ReleaseOwnership(out byte[]? buffer, out int start, out int length))
{
    // Lease giờ đã "rỗng" và sẽ không trả buffer về pool khi Dispose.
    // BẠN giờ chịu trách nhiệm trả 'buffer' về pool bằng tay!
    bufferPool.Return(buffer);
}

5. Danh sách kiểm tra hiệu năng

Hành độngMức ảnh hưởngVì sao?
Dùng using varQuan trọngĐảm bảo trả về pool một cách xác định.
Tránh lease.Span trong asyncQuan trọngSpan<T> không thể dùng qua ranh giới await.
Ưu tiên lease.MemoryCaoReadOnlyMemory<byte> an toàn cho async và không cấp phát bộ nhớ.
Khớp dung lượngTrung bìnhThuê kích thước gần với độ dài gói tin giúp giảm phân mảnh nội bộ.

Thông tin liên quan