Vô hiệu bộ nhớ cache - Có giải pháp chung không?


118

"Chỉ có hai vấn đề khó khăn trong Khoa học Máy tính: vô hiệu bộ nhớ cache và đặt tên cho mọi thứ."

Phil Karlton

Có giải pháp hoặc phương pháp chung nào để làm mất hiệu lực bộ đệm không; để biết khi nào một mục nhập đã cũ, vì vậy bạn được đảm bảo luôn nhận được dữ liệu mới?

Ví dụ: hãy xem xét một hàm getData()lấy dữ liệu từ một tệp. Nó lưu trữ nó dựa trên thời gian sửa đổi cuối cùng của tệp, nó sẽ kiểm tra mỗi khi nó được gọi.
Sau đó, bạn thêm một hàm thứ hai transformData()để biến đổi dữ liệu và lưu vào bộ nhớ cache kết quả của nó cho lần gọi hàm tiếp theo. Nó không có kiến ​​thức về tệp - làm thế nào để bạn thêm phần phụ thuộc mà nếu tệp bị thay đổi, bộ đệm này sẽ trở nên không hợp lệ?

Bạn có thể gọi getData()mỗi lần transformData()được gọi và so sánh nó với giá trị được sử dụng để tạo bộ nhớ cache, nhưng điều đó có thể rất tốn kém.


6
Tôi tin rằng anh ấy phải làm gì đó với việc viết X Windows
Greg

1
Tôi nghĩ rằng tiêu đề đó sẽ tốt hơn là "Vô hiệu bộ nhớ cache - Có giải pháp chung không?" vì nó đề cập đến một lớp cụ thể của vấn đề bộ nhớ đệm.
RBarryYoung 27/07/09

71
Không, anh ấy không biết nhiều về khoa học máy tính. Tôi chắc rằng việc anh ấy tham gia tạo OpenGL, X11 và SSLv3 khiến anh ấy quá bận rộn để thực sự nghiên cứu nhiều về nó. :-)
Tim Lesher

80
Chỉ có 2 vấn đề khó khăn trong khoa học máy tính: Mất hiệu lực bộ nhớ cache. Đặt tên cho sự vật. Và lỗi từng lỗi một.
The Dag

8
Tôi đã từng nghe điều này với tên"The two hardest things in Computer Science are cache invalidation, naming things, and off-by-one errors."
Jonathon Reinhart,

Câu trả lời:


55

Những gì bạn đang nói đến là chuỗi phụ thuộc suốt đời, rằng một thứ phụ thuộc vào một thứ khác có thể được sửa đổi ngoài tầm kiểm soát của nó.

Nếu bạn có một hàm Idempotent từ a, bđến cđâu, nếu abgiống nhau thì cgiống nhau nhưng chi phí kiểm tra bcao thì bạn:

  1. chấp nhận rằng đôi khi bạn hoạt động với thông tin lỗi thời và không phải lúc nào cũng kiểm tra b
  2. làm tốt nhất cấp độ của bạn để kiểm tra bnhanh nhất có thể

Bạn không thể cầm chiếc bánh của mình và ăn nó ...

Nếu bạn có thể xếp lớp một bộ đệm bổ sung dựa trên aphía trên thì điều này ảnh hưởng đến vấn đề ban đầu chứ không phải một chút nào. Nếu bạn chọn 1 thì bạn có bất kỳ quyền tự do nào mà bạn đã cho mình và do đó có thể lưu vào bộ nhớ cache nhiều hơn nhưng phải nhớ xem xét tính hợp lệ của giá trị được lưu trong bộ nhớ cache b. Nếu bạn chọn 2, bạn vẫn phải kiểm tra bmọi lúc nhưng có thể quay lại bộ nhớ cache anếu bkiểm tra hết.

Nếu bạn phân lớp bộ nhớ cache, bạn phải xem xét liệu bạn có vi phạm 'quy tắc' của hệ thống do hành vi kết hợp hay không.

Nếu bạn biết điều đó aluôn có giá trị nếu bcó thì bạn có thể sắp xếp bộ nhớ cache của mình như vậy (mã giả):

private map<b,map<a,c>> cache // 
private func realFunction    // (a,b) -> c

get(a, b) 
{
    c result;
    map<a,c> endCache;
    if (cache[b] expired or not present)
    {
        remove all b -> * entries in cache;   
        endCache = new map<a,c>();      
        add to cache b -> endCache;
    }
    else
    {
        endCache = cache[b];     
    }
    if (endCache[a] not present)     // important line
    {
        result = realFunction(a,b); 
        endCache[a] = result;
    }
    else   
    {
        result = endCache[a];
    }
    return result;
}

Rõ ràng lớp liên tiếp (nói x) là tầm thường chừng nào, ở từng giai đoạn tính hợp lệ của đầu vào mới được bổ sung phù hợp với a: bmối quan hệ cho x: bx: a.

Tuy nhiên, rất có thể bạn có thể nhận được ba đầu vào có giá trị hoàn toàn độc lập (hoặc theo chu kỳ), vì vậy sẽ không thể phân lớp. Điều này có nghĩa là dòng được đánh dấu // quan trọng sẽ phải thay đổi thành

nếu (endCache [a] hết hạn hoặc không có)


3
hoặc có thể, nếu chi phí kiểm tra b cao thì bạn dùng pubsub để khi b thay đổi sẽ thông báo cho c. Mẫu Observer là phổ biến.
user1031420

15

Vấn đề trong việc vô hiệu hóa bộ nhớ cache là nội dung thay đổi mà chúng tôi không biết về nó. Vì vậy, trong một số trường hợp, một giải pháp là khả thi nếu có một số thứ khác biết về nó và có thể thông báo cho chúng tôi. Trong ví dụ đã cho, hàm getData có thể kết nối vào hệ thống tệp, hệ thống này biết về tất cả các thay đổi đối với tệp, bất kể quá trình nào thay đổi tệp và đến lượt nó, thành phần này có thể thông báo cho thành phần biến đổi dữ liệu.

Tôi không nghĩ rằng có bất kỳ bản sửa lỗi phép thuật chung nào để làm cho vấn đề biến mất. Nhưng trong nhiều trường hợp thực tế, rất có thể có cơ hội để chuyển đổi cách tiếp cận dựa trên "thăm dò ý kiến" thành một cách tiếp cận dựa trên "gián đoạn", có thể làm cho vấn đề đơn giản biến mất.


3

Nếu bạn định getData () mỗi khi thực hiện chuyển đổi, thì bạn đã loại bỏ toàn bộ lợi ích của bộ đệm.

Đối với ví dụ của bạn, có vẻ như một giải pháp sẽ dành cho khi bạn tạo dữ liệu đã chuyển đổi, cũng lưu trữ tên tệp và thời gian sửa đổi cuối cùng của tệp mà dữ liệu được tạo từ đó (bạn đã lưu trữ dữ liệu này trong bất kỳ cấu trúc dữ liệu nào được trả về bởi getData ( ), vì vậy bạn chỉ cần sao chép bản ghi đó vào cấu trúc dữ liệu được trả về bởi TransformData ()) và sau đó khi bạn gọi lại biến TransitData (), hãy kiểm tra lần sửa đổi cuối cùng của tệp.


3

IMHO, Lập trình phản ứng chức năng (FRP) theo nghĩa là một cách chung để giải quyết tình trạng vô hiệu bộ nhớ cache.

Đây là lý do tại sao: dữ liệu cũ trong thuật ngữ FRP được gọi là trục trặc . Một trong những mục tiêu của FRP là đảm bảo không có trục trặc.

FRP được giải thích chi tiết hơn trong bài nói chuyện 'Bản chất của FRP' và trong câu trả lời SO này .

Trong bài nói chuyện, các Cells đại diện cho một Đối tượng / Thực thể được lưu trong bộ nhớ cache và a Cellđược làm mới nếu một trong các phần phụ thuộc của nó được làm mới.

FRP ẩn mã hệ thống ống nước được liên kết với biểu đồ phụ thuộc và đảm bảo rằng không có lỗi cũ Cell.


Một cách khác (khác với FRP) mà tôi có thể nghĩ đến là gói giá trị được tính toán (kiểu b) vào một số loại văn bản Monad Writer (Set (uuid)) btrong đó Set (uuid)(ký hiệu Haskell) chứa tất cả các mã nhận dạng của các giá trị có thể thay đổi mà giá trị được tính bphụ thuộc vào đó. Vì vậy, uuidlà một số loại mã định danh duy nhất xác định giá trị / biến có thể thay đổi (ví dụ một hàng trong cơ sở dữ liệu) mà giá trị được tính bphụ thuộc vào đó.

Kết hợp ý tưởng này với các tổ hợp hoạt động trên loại nhà văn Monad này và điều đó có thể dẫn đến một số loại giải pháp vô hiệu hóa bộ nhớ cache chung nếu bạn chỉ sử dụng các tổ hợp này để tính toán mới b. Các tổ hợp như vậy (ví dụ như một phiên bản đặc biệt của filter) lấy đơn nguyên và (uuid, a)-s của Writer làm đầu vào, trong đó alà một dữ liệu / biến có thể thay đổi, được xác định bởi uuid.

Vì vậy, mỗi khi bạn thay đổi dữ liệu "gốc" (uuid, a)(giả sử dữ liệu chuẩn hóa trong cơ sở dữ liệu bđược tính toán từ đó ) mà giá trị được tính toán của loại bphụ thuộc vào thì bạn có thể làm mất hiệu lực bộ đệm ẩn chứa bnếu bạn thay đổi bất kỳ giá trị nào abgiá trị được tính phụ thuộc vào , bởi vì dựa vào Set (uuid)Writer Monad, bạn có thể biết khi nào điều này xảy ra.

Vì vậy, bất cứ khi nào bạn thay đổi điều gì đó với một giá trị nhất định uuid, bạn sẽ phát đột biến này tới tất cả các bộ nhớ cache và chúng làm mất hiệu lực các giá trị bphụ thuộc vào giá trị có thể thay đổi được xác định với đã nói uuidbởi vì đơn nguyên Writer trong đó đơn nguyên bđược bao bọc có thể cho biết điều đó có bphụ thuộc vào đã nói uuidhoặc không phải.

Tất nhiên, điều này chỉ có lợi nếu bạn đọc thường xuyên hơn nhiều so với viết.


Cách tiếp cận thứ ba, thực tế, là sử dụng các khung nhìn cụ thể hóa trong cơ sở dữ liệu và sử dụng chúng như các khung nhìn bộ nhớ cache. AFAIK họ cũng nhằm giải quyết vấn đề vô hiệu. Tất nhiên, điều này giới hạn các hoạt động kết nối dữ liệu có thể thay đổi với dữ liệu dẫn xuất.


2

Hiện tôi đang làm việc trên một phương pháp dựa trên PostSharp và các chức năng ghi nhớ . Tôi đã chạy nó qua người cố vấn của mình và anh ấy đồng ý rằng đó là một cách triển khai bộ nhớ đệm theo cách bất khả tri về nội dung.

Mọi chức năng có thể được đánh dấu bằng một thuộc tính chỉ định thời gian hết hạn của nó. Mỗi hàm được đánh dấu theo cách này được ghi nhớ và kết quả được lưu trữ vào bộ nhớ đệm, với một hàm băm của lệnh gọi hàm và các tham số được sử dụng làm khóa. Tôi đang sử dụng Velocity cho phần phụ trợ, xử lý phân phối dữ liệu bộ nhớ cache.


1

Có giải pháp hoặc phương pháp chung nào để tạo bộ nhớ cache, để biết khi nào một mục nhập đã cũ, để bạn được đảm bảo luôn nhận được dữ liệu mới không?

Không, bởi vì tất cả dữ liệu đều khác nhau. Một số dữ liệu có thể "cũ" sau một phút, một số sau một giờ và một số có thể ổn trong vài ngày hoặc vài tháng.

Về ví dụ cụ thể của bạn, giải pháp đơn giản nhất là có chức năng 'kiểm tra bộ nhớ cache' cho các tệp mà bạn gọi từ cả hai getDatatransformData.


1

Không có giải pháp chung nhưng:

  • Bộ nhớ cache của bạn có thể hoạt động như một proxy (kéo). Giả sử bộ nhớ cache của bạn biết dấu thời gian của thay đổi nguồn gốc cuối cùng, khi ai đó gọi getData(), bộ nhớ cache sẽ hỏi nguồn gốc cho dấu thời gian của thay đổi cuối cùng đó, nếu giống nhau, nó trả về bộ nhớ cache, nếu không nó cập nhật nội dung của nó với nguồn một và trả lại nội dung của nó. (Một biến thể là ứng dụng khách gửi trực tiếp dấu thời gian theo yêu cầu, nguồn sẽ chỉ trả lại nội dung nếu dấu thời gian của nó khác.)

  • Bạn vẫn có thể sử dụng một tiến trình thông báo (push), bộ nhớ cache quan sát nguồn, nếu nguồn thay đổi, nó sẽ gửi thông báo đến bộ nhớ cache và sau đó được gắn cờ là "bẩn". Nếu ai đó gọi getData(), bộ nhớ cache sẽ được cập nhật nguồn đầu tiên, hãy xóa cờ "bẩn"; sau đó trả về nội dung của nó.

Nói chung, sự lựa chọn phụ thuộc vào:

  • Tần suất: nhiều cuộc gọi getData()muốn nhấn để tránh nguồn bị ngập bởi hàm getTimestamp
  • Quyền truy cập của bạn vào nguồn: Bạn có đang sở hữu mô hình nguồn không? Nếu không, rất có thể bạn không thể thêm bất kỳ quy trình thông báo nào.

Lưu ý: Vì sử dụng dấu thời gian là cách truyền thống của proxy http đang hoạt động, nên một cách tiếp cận khác là chia sẻ hàm băm của nội dung được lưu trữ. Cách duy nhất mà tôi biết để 2 thực thể được cập nhật cùng nhau là tôi gọi bạn (kéo) hoặc bạn gọi tôi… (đẩy) vậy thôi.



-2

Có lẽ các thuật toán bỏ qua bộ nhớ cache sẽ là thuật toán chung nhất (Hoặc ít nhất, ít phụ thuộc vào cấu hình phần cứng hơn), vì chúng sẽ sử dụng bộ nhớ cache nhanh nhất trước tiên và tiếp tục từ đó. Đây là một bài giảng của MIT về nó: Các thuật toán rõ ràng trong bộ nhớ cache


3
Tôi nghĩ rằng anh ấy không nói về bộ nhớ đệm phần cứng - anh ấy đang nói về mã getData () của anh ấy có một tính năng "lưu trữ" dữ liệu anh ấy nhận được từ một tệp vào bộ nhớ.
Alex319,
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.