7,8 MB khóa mà tôi đã cấp phát chỉ để vứt bỏ
Một công cụ profiler đã chỉ ra một chi phí ẩn trong mã nguồn của tôi.
Trình xử lý yêu cầu (request handler) của tôi đọc các token từ một chuỗi khổng lồ và tính tổng trọng số của chúng. Logic thì chạy đúng, nhưng bộ nhớ thì không.
Bên trong một vòng lặp nóng (hot loop), tôi đã cấp phát 7,8 MB chuỗi—mỗi lần tra cứu từ điển lại tạo một khóa mới—rồi vứt bỏ khóa đó ngay lập tức.
Thủ phạm chính là Substring. Mỗi lát cắt (slice) đều tạo ra một đối tượng chuỗi mới.
200.000 token
- Phương pháp A (Substring): 23,4 ms, 7.812 KB đã cấp phát.
- Phương pháp B (Span Lookup): 14,9 ms, 0 KB đã cấp phát.
Phương pháp B chạy nhanh hơn 1,5 lần và không tạo ra rác (garbage).
Trong .NET 9, bạn có thể gọi GetAlternateLookup. Thay vì một chuỗi mới, bạn truyền vào một ReadOnlySpan<char>, vốn chỉ là một "cửa sổ" nhìn vào chuỗi gốc—không cần sao chép.
Từ điển sẽ băm (hash) trực tiếp span đó với các khóa hiện có, mang lại cùng một kết quả mà không cần cấp phát bộ nhớ.
Những điều cần lưu ý
- Hoạt động với
StringComparer.OrdinalvàStringComparer.OrdinalIgnoreCase. - Sẽ ném ra ngoại lệ tại thời điểm chạy (runtime) nếu bạn sử dụng một bộ so sánh tùy chỉnh (custom comparer) không hỗ trợ tra cứu thay thế (alternate lookup).
- Lý tưởng cho các luồng xử lý quan trọng (hot paths) như bộ phân tích (parsers), bộ xử lý nhật ký (log processors), hoặc trình quét CSV.
- Bỏ qua nếu chỉ là các tra cứu đơn giản với ít khóa.
Giờ đây, tôi luôn rà soát mã nguồn để tìm các đoạn Substring theo sau bởi TryGetValue. Mô hình đó gây lãng phí một lượng lớn bộ nhớ.
Nguồn: https://dev.to/ssukhpinder/78-mb-of-keys-i-allocated-just-to-throw-away-50mn
Cộng đồng học tập tùy chọn: https://github.com/ssukhpinder/dev-to-code-samples/tree/main/023-dictionary-alternate-lookup
