Tôi nên sử dụng cấu trúc dữ liệu nào cho chiến lược lưu trữ này?


11

Tôi đang làm việc trên một ứng dụng .NET 4.0, thực hiện một phép tính khá tốn kém trên hai nhân đôi trả lại gấp đôi. Tính toán này được thực hiện cho mỗi một trong vài ngàn mục . Những tính toán này được thực hiện trong một Taskluồng trên luồng.

Một số thử nghiệm sơ bộ đã chỉ ra rằng các phép tính tương tự được thực hiện lặp đi lặp lại, vì vậy tôi muốn lưu trữ kết quả n . Khi bộ nhớ cache đầy, tôi muốn loại bỏ mục ít được sử dụng gần đây nhất. ( Edit: Tôi nhận ra kém thường không có ý nghĩa, bởi vì khi bộ nhớ cache là đầy đủ và tôi sẽ thay thế Kết quả là với một mới tính toán, mà người ta sẽ được ít nhất thường được sử dụng và ngay lập tức thay thế thời gian tới một kết quả mới được tính và thêm vào bộ đệm)

Để thực hiện điều này, tôi đã nghĩ đến việc sử dụng một Dictionary<Input, double>(trong đó Inputsẽ là một lớp nhỏ lưu trữ hai giá trị kép đầu vào) để lưu trữ các đầu vào và kết quả được lưu trong bộ nhớ cache. Tuy nhiên, tôi cũng cần theo dõi khi nào kết quả được sử dụng lần cuối. Đối với điều này, tôi nghĩ rằng tôi sẽ cần một bộ sưu tập thứ hai lưu trữ thông tin mà tôi sẽ cần để xóa một kết quả khỏi dictonary khi bộ đệm đã đầy. Tôi lo ngại rằng việc liên tục giữ danh sách này được sắp xếp sẽ ảnh hưởng tiêu cực đến hiệu suất.

Có cách nào tốt hơn (tức là hiệu quả hơn) để làm điều này, hoặc thậm chí có thể là một cấu trúc dữ liệu phổ biến mà tôi không biết? Những loại điều tôi nên được định hình / đo lường để xác định sự tối ưu của giải pháp của tôi?

Câu trả lời:


12

Nếu bạn muốn sử dụng bộ đệm trục xuất LRU (đuổi tối thiểu được sử dụng gần đây), thì có lẽ một sự kết hợp tốt của các cấu trúc dữ liệu sẽ sử dụng là:

  • Danh sách liên kết tròn (như một hàng đợi ưu tiên)
  • Từ điển

Đây là lý do tại sao:

  • Danh sách được liên kết có thời gian chèn và xóa O (1)
  • Các nút danh sách có thể được sử dụng lại khi danh sách đầy và không cần phân bổ thêm.

Đây là cách thuật toán cơ bản nên hoạt động:

Các cấu trúc dữ liệu

LinkedList<Node<KeyValuePair<Input,Double>>> list; Dictionary<Input,Node<KeyValuePair<Input,Double>>> dict;

  1. Đầu vào được nhận
  2. Nếu từ điển chứa khóa
    • trả về giá trị được lưu trong nút và di chuyển nút đến đầu danh sách
  3. Nếu từ điển không chứa khóa
    • tính giá trị
    • lưu trữ giá trị trong nút cuối cùng của danh sách
    • nếu cái cuối cùng không có giá trị, hãy xóa khóa trước khỏi từ điển
    • di chuyển nút cuối cùng đến vị trí đầu tiên.
    • lưu trữ trong từ điển cặp giá trị khóa (đầu vào, nút).

Một số lợi ích của phương pháp này là, đọc và thiết lập giá trị từ điển tiếp cận O (1), chèn và xóa nút trong danh sách được liên kết là O (1), có nghĩa là thuật toán đang tiếp cận O (1) để đọc và ghi giá trị vào bộ đệm và tránh phân bổ bộ nhớ và chặn các hoạt động sao chép bộ nhớ, làm cho nó ổn định theo quan điểm bộ nhớ.


Điểm tốt, ý tưởng tốt nhất cho đến nay, IMHO. Tôi đã thực hiện một bộ đệm dựa trên điều này ngày hôm nay và sẽ phải lập hồ sơ và xem nó hoạt động tốt như thế nào vào ngày mai.
PersonalNexus

3

Điều này có vẻ như rất nhiều nỗ lực để thực hiện một phép tính duy nhất dựa trên sức mạnh xử lý mà bạn có trong máy tính trung bình. Ngoài ra, Bạn vẫn sẽ có các chi phí của các cuộc gọi đầu tiên để tính toán của bạn cho mỗi cặp giá trị độc đáo, vì vậy 100.000 cặp giá trị duy nhất vẫn sẽ chi phí bạn Thời gian n * 100.000 ở mức tối thiểu. Hãy xem xét rằng việc truy cập các giá trị trong từ điển của bạn sẽ có thể trở nên chậm hơn khi từ điển phát triển lớn hơn. Bạn có thể đảm bảo tốc độ truy cập từ điển của bạn sẽ bù đủ để cung cấp lợi nhuận hợp lý so với tốc độ tính toán của bạn không?

Bất kể, có vẻ như bạn có thể sẽ cần phải xem xét việc tìm một phương tiện để tối ưu hóa thuật toán của bạn. Đối với điều này, bạn sẽ cần một công cụ định hình, chẳng hạn như Redgate Ant để xem các nút thắt cổ chai ở đâu và để giúp bạn xác định xem có cách nào để giảm một số chi phí bạn có thể có liên quan đến tính năng tức thời của lớp, liệt kê danh sách, cơ sở dữ liệu truy cập, hoặc bất cứ điều gì đang làm bạn tốn rất nhiều thời gian.


1
Thật không may, hiện tại thuật toán tính toán không thể thay đổi, vì đây là thư viện của bên thứ ba sử dụng một số toán học nâng cao, vốn rất tốn CPU. Nếu sau đó sẽ được làm lại, tôi chắc chắn sẽ kiểm tra các công cụ định hình được đề xuất. Hơn nữa, việc tính toán sẽ được thực hiện khá thường xuyên, đôi khi với các đầu vào giống hệt nhau, vì vậy hồ sơ sơ bộ đã cho thấy một lợi ích rõ ràng ngay cả với một chiến lược lưu trữ rất ngây thơ.
PersonalNexus

0

Một suy nghĩ là tại sao chỉ có kết quả cache n? Ngay cả khi n là 300.000, bạn sẽ chỉ sử dụng 7,2 MB bộ nhớ (cộng thêm bất cứ thứ gì thêm cho cấu trúc bảng). Tất nhiên, giả sử ba nhân đôi 64 bit. Bạn chỉ có thể áp dụng ghi nhớ cho chính thói quen vôi hóa phức tạp nếu bạn không lo lắng về việc hết dung lượng bộ nhớ.


Sẽ không chỉ có một bộ đệm, nhưng một bộ cho mỗi "mục" mà tôi đang phân tích, và có thể có hàng trăm nghìn mục này.
PersonalNexus

Theo cách nào thì vấn đề 'Mục' đầu vào đến từ đâu? Có tác dụng phụ?
jk.

@jk. Các mặt hàng khác nhau sẽ tạo ra đầu vào rất khác nhau để tính toán. Vì điều này có nghĩa là sẽ có một chút trùng lặp, tôi không nghĩ việc giữ chúng trong một bộ đệm duy nhất có ý nghĩa. Hơn nữa, các mục khác nhau có thể sống trong các luồng khác nhau, vì vậy để tránh trạng thái chia sẻ, tôi muốn tách riêng bộ đệm.
PersonalNexus

@PersonalNexus Tôi lấy điều này để ngụ ý có nhiều hơn 2 tham số trong phép tính? Mặt khác, về cơ bản bạn vẫn có f (x, y) = làm một số thứ. Cộng với trạng thái chia sẻ có vẻ như nó sẽ giúp hiệu suất hơn là cản trở?
Peter Smith

@PeterSmith Hai tham số là đầu vào chính. Có những người khác, nhưng họ hiếm khi thay đổi. Nếu họ làm, tôi sẽ ném toàn bộ bộ đệm. Theo "trạng thái chia sẻ" Tôi có nghĩa là một bộ đệm được chia sẻ cho tất cả hoặc một nhóm các mục. Vì điều này sẽ cần phải được khóa hoặc đồng bộ hóa theo một cách khác, nó sẽ cản trở hiệu suất. Thêm về ý nghĩa hiệu suất của nhà nước chia sẻ .
PersonalNexus

0

Cách tiếp cận với bộ sưu tập thứ hai là tốt. Nó phải là một hàng đợi ưu tiên cho phép tìm / xóa các giá trị tối thiểu một cách nhanh chóng và cũng thay đổi (tăng) các ưu tiên trong hàng đợi (phần sau là phần cứng, không được hỗ trợ bởi hầu hết các triển khai hàng đợi đơn giản). Các thư viện C5 có một bộ sưu tập như vậy, nó được gọi là IntervalHeap.

Hoặc tất nhiên, bạn có thể cố gắng xây dựng bộ sưu tập của riêng bạn, đại loại như a SortedDictionary<int, List<InputCount>>. ( InputCountphải là lớp kết hợp Inputdữ liệu của bạn với Countgiá trị của bạn )

Cập nhật bộ sưu tập đó khi thay đổi giá trị đếm của bạn có thể được thực hiện bằng cách xóa và chèn lại một phần tử.


0

Như đã chỉ ra trong câu trả lời của Peter Smith, mô hình mà bạn đang cố gắng thực hiện được gọi là ghi nhớ . Trong C #, thật khó để thực hiện ghi nhớ một cách minh bạch mà không có tác dụng phụ. Cuốn sách của Oliver Sturm về lập trình chức năng trong C # đưa ra một giải pháp (mã có sẵn để tải xuống, chương 10).

Trong F # nó sẽ dễ dàng hơn nhiều. Tất nhiên, đó là một quyết định lớn khi bắt đầu sử dụng ngôn ngữ lập trình khác, nhưng có thể đáng để xem xét. Đặc biệt là trong các tính toán phức tạp, nhất định sẽ làm cho nhiều thứ dễ lập trình hơn là ghi nhớ.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.