6

Giả sử rằng tôi có các đối tượng sau đâyQuản lý bộ nhớ/bộ nhớ đệm cho các đối tượng tốn kém trong C#

public class MyClass 
{ 
    public ReadOnlyDictionary<T, V> Dict 
    { 
     get 
     { 
      return createDictionary(); 
     } 
    } 
} 

Giả sử rằng ReadOnlyDictionary là một chỉ đọc bao bọc xung quanh Dictionary<T, V>.

Phương thức createDictionary mất nhiều thời gian để hoàn thành và trả lại từ điển tương đối lớn.

Rõ ràng, tôi muốn triển khai một số loại bộ nhớ đệm để tôi có thể tái sử dụng kết quả của createDictionary nhưng cũng không muốn lạm dụng bộ thu gom rác và sử dụng nhiều bộ nhớ.

Tôi đã nghĩ đến việc sử dụng WeakReference cho từ điển nhưng không chắc chắn nếu đây là phương pháp tốt nhất.

Bạn sẽ đề xuất điều gì? Làm thế nào để xử lý đúng kết quả của một phương pháp tốn kém có thể được gọi là nhiều lần?

CẬP NHẬT:

Tôi quan tâm đến lời khuyên cho thư viện C# 2.0 (đơn DLL, không trực quan). Thư viện có thể được sử dụng trong một máy tính để bàn của một ứng dụng web.

CẬP NHẬT 2:

Câu hỏi này cũng phù hợp với các đối tượng chỉ đọc. Tôi đã thay đổi giá trị của thuộc tính từ Dictionary thành ReadOnlyDictionary.

UPDATE 3:

Các T là loại tương đối đơn giản (string, ví dụ). V là một lớp tùy chỉnh. Bạn có thể giả định rằng một thể hiện của V là tốn kém để tạo. Từ điển có thể chứa từ 0 đến vài nghìn phần tử.

Mã được giả định được truy cập từ một chuỗi đơn lẻ hoặc từ nhiều luồng với cơ chế đồng bộ hóa bên ngoài.

Tôi ổn nếu từ điển là GC-ed khi không ai sử dụng nó. Tôi đang cố gắng để tìm một sự cân bằng giữa thời gian (tôi muốn bằng cách nào đó bộ nhớ cache kết quả của createDictionary) và chi phí bộ nhớ (tôi không muốn giữ bộ nhớ bị chiếm đóng lâu hơn cần thiết).

+1

Điều gì về việc sử dụng các hệ thống bên ngoài? memcached (với hết hạn) có thể? – eyossi

+0

Đây có phải là ứng dụng dành cho web hoặc máy tính để bàn không? – McGarnagle

+0

@dbaseman vui lòng xem cập nhật của tôi cho câu hỏi. – Bobrovsky

Trả lời

1

Có bốn cơ chế chính có sẵn cho bạn (Lazy do thỏa thuận 4,0, vì vậy nó không có tùy chọn)

  1. lười biếng khởi
  2. Proxy ảo
  3. ma
  4. giữ giá trị

đều có lợi thế riêng.

tôi đề xuất trình giữ giá trị, điền vào từ điển trong lần gọi đầu tiên của phương thức GetValue của chủ sở hữu. sau đó bạn có thể sử dụng giá trị đó miễn là bạn muốn VÀ nó chỉ là được thực hiện một lần VÀ nó chỉ được thực hiện khi có nhu cầu.

để biết thêm thông tin, xem martin fowlers page

0

Tôi nghĩ bạn có hai cơ chế mà bạn có thể dựa vào để lưu vào bộ nhớ đệm, thay vì phát triển bộ nhớ của riêng bạn. Đầu tiên, như bạn đã đề xuất, là sử dụng một WeakReference, và để cho bộ thu gom rác quyết định khi nào giải phóng bộ nhớ này.

Bạn có cơ chế thứ hai - phân trang bộ nhớ. Nếu từ điển được tạo ra trong một swoop, nó có thể sẽ được lưu trữ trong một phần ít nhiều liên tục của đống. Chỉ cần giữ cho từ điển còn sống, và để cho Windows trang nó ra tệp hoán đổi nếu bạn không cần nó. Tùy thuộc vào cách sử dụng của bạn (truy cập từ điển của bạn ngẫu nhiên như thế nào), bạn có thể kết thúc với hiệu suất tốt hơn so với WeakReference.

Cách tiếp cận thứ hai này có vấn đề nếu bạn gần với giới hạn không gian địa chỉ của mình (điều này chỉ xảy ra trong các quy trình 32 bit).

+1

Xin lỗi nhưng WeakReference sẽ được xóa ngay sau khi không ai khác tham khảo từ điển này sẽ khiến bộ nhớ cache của bạn vô dụng. Phân trang không giúp được gì vì heap được quản lý (gần như) không bao giờ được phân trang vì GC không chạm vào tất cả GC Heaps khá thường xuyên. –

+0

Bạn có nói các chương trình .NET không bao giờ được phân trang không? – zmbq

+0

Nếu bạn đang thực hiện phân bổ thường xuyên, các cơ hội mờ đi mà phần lớn số lượng GC của bạn sẽ được phân trang. Mặc dù có những cơ hội mà bạn có thể đạt được khi bạn có một mẫu phân bổ cụ thể. GC sử dụng GetWriteWatch Api để kiểm tra xem một vùng bộ nhớ đã bị thay đổi kể từ khi nó được truy cập lần cuối vì vậy (theo lý thuyết) nó không cần chạm vào nó một lần nữa. –

3

WeakReference không phải là giải pháp tốt cho bộ nhớ cache vì bạn phản đối sẽ không tồn tại trong GC tiếp theo nếu không ai khác đang tham khảo từ điển của bạn. Bạn có thể tạo một bộ nhớ đệm đơn giản bằng cách lưu trữ giá trị đã tạo trong biến thành viên và sử dụng lại nó nếu nó không phải là null.

Đây không phải là chủ đề an toàn và bạn sẽ kết thúc trong một số trường hợp tạo từ điển nhiều lần nếu bạn có quyền truy cập đồng thời nặng vào nó. Bạn có thể sử dụng mẫu khóa được kiểm tra kép để bảo vệ chống lại điều này với tác động tối thiểu.

Để giúp bạn tiếp tục, bạn sẽ cần xác định xem truy cập đồng thời có phải là vấn đề cho bạn không và lượng bộ nhớ mà từ điển của bạn tiêu thụ và cách bộ nhớ được tạo. Nếu ví dụ: từ điển là kết quả của một truy vấn đắt tiền nó có thể giúp đơn giản sắp xếp từng từ điển vào đĩa và tái sử dụng nó cho đến khi bạn cần tạo lại nó (điều này phụ thuộc vào nhu cầu cụ thể của bạn).

Caching là một từ khác cho rò rỉ bộ nhớ nếu bạn không có chính sách rõ ràng khi đối tượng của bạn sẽ bị xóa khỏi bộ nhớ cache. Kể từ khi bạn đang cố gắng WeakReference tôi giả sử bạn không biết khi nào chính xác một thời gian tốt sẽ được để xóa bộ nhớ cache.

Một tùy chọn khác là nén từ điển vào cấu trúc kém bộ nhớ. Từ điển của bạn có bao nhiêu khóa và giá trị là gì?

+0

vui lòng xem cập nhật của tôi cho câu hỏi. – Bobrovsky

+0

Bạn nói V tốn kém để tạo. Từ điển có luôn được truy cập đầy đủ không? Nếu không, bạn chỉ có thể giữ một danh sách được sử dụng gần đây nhất của Vs trong bộ nhớ cache của bạn để tính phần còn lại. Hoặc thậm chí tốt hơn bạn có thể tạo một từ điển của K, Lazy trong đó giá trị được tạo cho từ điển chỉ khi nó được truy cập. –

+0

Từ điển có thể được truy cập theo cách tùy ý (Tôi không có dữ liệu về tần suất truy cập). Ý tưởng của bạn về Lazy là quan tâm, cảm ơn bạn. Tuy nhiên, không chắc chắn nó sẽ ảnh hưởng đến GC như thế nào. – Bobrovsky

1

Bạn có chắc chắn bạn cần bộ nhớ cache toàn bộ từ điển?

Từ những gì bạn nói, có thể nên giữ danh sách cặp khóa-giá trị được sử dụng gần đây nhất.

Nếu khóa được tìm thấy trong danh sách, chỉ cần trả về giá trị.

Nếu không, hãy tạo một giá trị (được cho là nhanh hơn khi tạo tất cả chúng và sử dụng ít bộ nhớ hơn) và lưu nó trong danh sách, do đó xóa cặp khóa-giá trị chưa được sử dụng dài nhất.

Dưới đây là một danh sách MRU rất đơn giản thực hiện, nó có thể đóng vai trò là nguồn cảm hứng:

using System.Collections.Generic; 
using System.Linq; 

internal sealed class MostRecentlyUsedList<T> : IEnumerable<T> 
{ 
    private readonly List<T> items; 
    private readonly int maxCount; 

    public MostRecentlyUsedList(int maxCount, IEnumerable<T> initialData) 
     : this(maxCount) 
    { 
     this.items.AddRange(initialData.Take(maxCount)); 
    } 

    public MostRecentlyUsedList(int maxCount) 
    { 
     this.maxCount = maxCount; 
     this.items = new List<T>(maxCount); 
    } 

    /// <summary> 
    /// Adds an item to the top of the most recently used list. 
    /// </summary> 
    /// <param name="item">The item to add.</param> 
    /// <returns><c>true</c> if the list was updated, <c>false</c> otherwise.</returns> 
    public bool Add(T item) 
    { 
     int index = this.items.IndexOf(item); 

     if (index != 0) 
     { 
      // item is not already the first in the list 
      if (index > 0) 
      { 
       // item is in the list, but not in the first position 
       this.items.RemoveAt(index); 
      } 
      else if (this.items.Count >= this.maxCount) 
      { 
       // item is not in the list, and the list is full already 
       this.items.RemoveAt(this.items.Count - 1); 
      } 

      this.items.Insert(0, item); 

      return true; 
     } 
     else 
     { 
      return false; 
     } 
    } 

    public IEnumerator<T> GetEnumerator() 
    { 
     return this.items.GetEnumerator(); 
    } 

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 
    { 
     return this.GetEnumerator(); 
    } 
} 

Trong trường hợp của bạn, T là một cặp khóa-giá trị. Giữ số lượng tối đa đủ nhỏ để tìm kiếm luôn nhanh và tránh sử dụng bộ nhớ quá mức. Gọi Thêm mỗi khi bạn sử dụng một mục.

+0

Danh sách MRU của bạn có một số vấn đề. Trước tiên, bạn nên cache từ điển là tốt. Nếu không, bạn sẽ gặp khó khăn trong việc tìm kiếm đúng V cho một khóa nhất định. Thứ hai, bạn dựa vào trình so sánh mặc định cho V, theo mặc định là bình đẳng tham chiếu sai. Bạn không thể giả định rằng V thực hiện GetHashCode và Equals đúng nếu nó được sử dụng làm giá trị trong từ điển. Thứ ba nếu bạn làm cache nhiều mục List.Contains trở nên rất chậm. Một Hashset có thể nhanh hơn với chi phí của bộ nhớ addtional mà bạn cần để giữ cho Vs trong một HashSet thêm. –

+0

Tôi biết nó có vấn đề. Đó là lý do tại sao tôi nói "một danh sách MRU rất đơn giản, nó có thể là nguồn cảm hứng" và "trong trường hợp của bạn, T là một cặp khóa-giá trị". Quan điểm của tôi là: có thể bạn không cần phải lưu trữ toàn bộ từ điển, chỉ các mục được sử dụng gần đây. –

+0

Đúng vậy. Để tối ưu hóa chính xác, chúng tôi sẽ có nhiều dữ liệu hơn nếu có sự cố thực tế hoặc nếu chúng tôi đang cố gắng khắc phục sự cố không phải. –

1

Ứng dụng nên sử dụng WeakReference làm cơ chế lưu bộ nhớ cache nếu tuổi thọ hữu ích của sự hiện diện của đối tượng trong bộ nhớ cache sẽ được so sánh với tuổi thọ tham chiếu của đối tượng. Giả sử, ví dụ, rằng bạn có một phương pháp sẽ tạo ra một ReadOnlyDictionary dựa trên deserializing một String.Nếu một mẫu sử dụng phổ biến sẽ là đọc một chuỗi, hãy tạo một từ điển, làm một số thứ với nó, từ bỏ nó và bắt đầu lại bằng một chuỗi khác, WeakReference có lẽ không lý tưởng. Mặt khác, nếu mục tiêu của bạn là deserialize nhiều chuỗi (khá một vài trong số đó sẽ được bình đẳng) vào ReadOnlyDictionary trường hợp, nó có thể rất hữu ích nếu lặp đi lặp lại cố gắng để deserialize cùng một chuỗi sản lượng dụ tương tự. Lưu ý rằng các khoản tiết kiệm sẽ không chỉ xuất phát từ thực tế là người ta chỉ phải làm công việc xây dựng cá thể một lần, mà còn từ các sự kiện rằng (1) nó sẽ không cần thiết để giữ nhiều thể hiện trong bộ nhớ, và (2) nếu ReadOnlyDictionary biến tham chiếu đến cùng một trường hợp, chúng có thể được coi là tương đương mà không phải tự kiểm tra các cá thể. Ngược lại, xác định xem hai cá thể ReadOnlyDictionary riêng biệt có tương đương nhau có thể yêu cầu kiểm tra tất cả các mục trong mỗi trường hợp không. Mã mà sẽ phải làm nhiều so sánh như vậy có thể được hưởng lợi từ việc sử dụng bộ nhớ cache WeakReference để các biến giữ các phiên bản tương đương thường sẽ giữ cùng một phiên bản.

Các vấn đề liên quan