LinQ Performace - Tối ưu bộ nhớ với yield return và file stream

Trong nhiều ứng dụng từ đơn giản đến phức tạp, thường rất phổ biến việc đọc dữ liệu từ các files dữ liệu nguồn (text, excel, ...), tiếp đó là các bước xử lý dữ liệu thô và cuối cùng là hiển thị hoặc lưu lại dữ liệu đã đươc xử lý. Các files dữ liệu nguồn thường tương đối lớn, chứa rất nhiều dòng text.

Để giải quyết bài toán này, các developers thường làm theo dạng như sau:
 

public List<T> ReadInformationFromFile(string fileName)
{
  string[] lines = File.ReadAllLines(fileName);
  List<T> result = new List<T>();
 
  foreach (var line in lines)
  {
    T obj = ProcessText(line);
    result.Add(obj); 
  }
   
  return result;
}

Khi execute code, hàm trên sẽ 1 danh sách gồm toàn bộ dữ liệu đã được xử lý, sau đó mới kết thúc. Đoạn mã theo dạng như snippet trên, là 'ok' về mặt logic, rõ ràng về cách xử lý. Tuy nhiên, cách làm trên sẽ không được chấp nhận ở những công việc đòi hỏi phải tối ưu về memory (thường là các hệ thống phải triển khai trên máy chủ của khách hàng hoặc trên máy tính cá nhân của 1 khách hàng - những nơi mà bạn sẽ rất khó để yêu cầu khách hàng cho phép truy xuất vào để kiểm tra khi có lỗi hoặc yêu cầu tăng dung lượng bộ nhớ). 

Để giải quyết tình huống này, 1 cách 'khôn ngoan' hơn là hãy dùng yield return. Nó sẽ giúp cho ứng dụng hoạt động tốt hơn trong những trường hợp low memory hoặc bad memory usage (dạng bazt simpson, memory leak). 

Đoạn mã trên sẽ được chỉnh lại như theo dạng sau:
 

public IEnumerable<T> ReadInformationFromFile(string fileName)
{
  using (StreamReader sr = new StreamReader(fileName))
  {
        while ((line = sr.ReadLine()) != null)
        {
            T obj = Process(line);
            yield return obj;
        }
  }
}

Với hàm này, file được đọc từng dòng, đọc đến dòng nào xử lý và trả về đến đó, không phỉa đợi xử lý xong tất cả các line rồi mới trả về kết quả. Việc xử lý như vậy sẽ giúp memory 'chịu đựng' tốt hơn khi hệ thống hoạt động ở chế độ 'high performance'. Khi dùng yield, .Net/NetCore platform sẽ compile method lại thành 1 state machine (có thể tham khảo state machine design pattern), implement các method Next, Current, … của IEnumerator.


Hi vọng bài viết sẽ giúp ích cho các bạn trong công việc !


Tác giả: Nguyễn Thanh Sơn

Chú ý: Tất cả các bài viết trên TEDU.COM.VN đều thuộc bản quyền TEDU, yêu cầu dẫn nguồn khi trích lại trên website khác.

Lên trên