SearchValues<T> cuối cùng cũng cho thấy lợi thế rõ rệt trong .NET 10 khi quét các bộ đệm lớn để tìm các dấu phân cách thưa thớt, với SearchValues hoàn thành trong 3,3 ms so với 5,6 ms của IndexOfAny trên một payload 32 MB.
Tại sao SearchValues ra đời
.NET 8 đã giới thiệu SearchValues<T> để tính toán trước một tập hợp các ký tự (hoặc byte) và cho phép runtime chọn thuật toán quét nhanh nhất cho tập hợp đó. Hãy khởi tạo đối tượng một lần, sau đó tái sử dụng bất cứ khi nào bạn cần tìm bất kỳ giá trị nào trong một span. Runtime có thể sử dụng các lệnh vector hóa (vectorized instructions), các vòng lặp không rẽ nhánh (branch-free loops), hoặc các thủ thuật cấp thấp khác mà các giải pháp viết tay không thể sánh kịp nếu không tốn rất nhiều công sức.
Các điểm chuẩn (benchmarks) quan trọng
Các bài kiểm tra khơi mào cuộc thảo luận đã sử dụng một bộ đệm 32 MB chứa năm ký tự phân cách. Ba phương pháp đã được đo thời gian trên .NET 10:
- IndexOfAny(char[]) – 10,2 ms
- Vòng lặp foreach thủ công trên span – 19,4 ms
- SearchValues
– 9,7 ms
SearchValues chỉ vượt qua IndexOfAny tích hợp sẵn khoảng nửa mili giây. Kết quả này không phải là tuyên bố "nhanh gấp năm lần" đầy kịch tính thường thấy trên các diễn đàn; .NET hiện đại đã tối ưu hóa IndexOfAny cho các tập hợp nhỏ, giúp thu hẹp khoảng cách này.
Khi các dấu phân cách xuất hiện thưa thớt—cứ mỗi 40 KB mới xuất hiện một lần thay vì mỗi 60 byte—bức tranh sẽ thay đổi:
- IndexOfAny(char[]) – 5,6 ms
- SearchValues
– 3,3 ms
Lúc này SearchValues nhanh hơn 1,7 lần vì engine có thể lướt qua các đoạn dữ liệu không khớp dài bằng các bước vector hóa, trong khi IndexOfAny phải chuyển sang một lộ trình ít quyết liệt hơn.
Chi phí ẩn
SearchValues không hề miễn phí. Việc khởi tạo đối tượng sẽ cấp phát bộ nhớ và xây dựng các bảng tra cứu (lookup tables) nội bộ. Nếu bạn khởi tạo nó trong mỗi lần gọi phương thức, chi phí overhead sẽ lấn át bất kỳ lợi ích hiệu năng nào. Một bài kiểm tra gọi quy trình quét một triệu lần trên các dòng ngắn cho thấy:
- SearchValues tĩnh (được tái sử dụng) – tổng cộng 25,1 ms
- SearchValues khởi tạo theo mỗi lần gọi – tổng cộng 70,2 ms
Việc tạo đối tượng cho mỗi lần gọi khiến toàn bộ hoạt động chậm hơn gấp ba lần so với việc không sử dụng SearchValues chút nào. Hãy lưu trữ instance trong một trường static readonly hoặc tái sử dụng nó qua các lần gọi.
Khi nào nên dùng SearchValues
- Đầu vào lớn, ít kết quả khớp – Lộ trình vector hóa phát huy tác dụng khi trình quét có thể bỏ qua các đoạn dài mà không gặp dấu phân cách.
- Quét lặp lại với cùng một tập hợp – Nếu cùng một tập hợp ký tự được tìm kiếm nhiều lần, việc thiết lập một lần duy nhất sẽ mang lại hiệu quả.
Khi nào nên tiếp tục dùng IndexOfAny
- Chuỗi ngắn hoặc có nhiều kết quả khớp – Chi phí thiết lập thêm sẽ lớn hơn mức tăng tốc độ không đáng kể.
- Mã nguồn đã sử dụng IndexOfAny – Bản triển khai của .NET hiện đại đã được tinh chỉnh rất kỹ cho các tập hợp ký tự nhỏ, vì vậy việc chuyển sang
SearchValuescó thể không mang lại sự khác biệt đáng kể.
Các sai lầm thường gặp
- Đặt việc khởi tạo bên trong các vòng lặp nóng (hot loops) – Dẫn đến việc chậm đi gấp ba lần như đã nêu ở trên.
- Cố gắng "thông minh hơn" runtime bằng các vòng lặp thủ công – Phiên bản
foreachthủ công chậm hơn gấp đôi so với cả hai phương pháp tích hợp sẵn, xác nhận rằng các thư viện .NET rất khó bị đánh bại nếu không có kiến thức sâu về các lệnh SIMD. - Giả định rằng nó sẽ tăng tốc trong mọi trường hợp – Việc chỉ thắng được nửa mili giây trên dữ liệu dày đặc chứng minh rằng
SearchValueskhông phải là một "mã gian lận" vạn năng; lợi ích của nó phụ thuộc vào dữ liệu.
Kết luận
SearchValues<T> chỉ mang lại lợi thế rõ rệt khi bạn quét các bộ đệm lớn để tìm các ký tự ít xuất hiện và có thể chấp nhận chi phí khởi tạo một lần. Đối với đa số các kịch bản tìm kiếm chuỗi thông thường—đầu vào ngắn, kết quả khớp thường xuyên, hoặc mã nguồn đã dựa vào IndexOfAny—phương pháp tích hợp sẵn vẫn là lựa chọn đơn giản và nhanh hơn. Hãy sử dụng SearchValues một cách có chọn lọc, lưu bộ nhớ đệm (cache) cho instance, và bạn sẽ tránh được hình phạt ẩn trong khi vẫn thu được lợi ích hiệu năng thực sự.
Mã nguồn và bộ kiểm thử đầy đủ: https://dev.to/ssukhpinder/when-searchvalues-actually-pays-off-310l
