Proszę nie mówić o EHCache lub OSCache, itp. Załóżmy na potrzeby tego pytania, że chcę zaimplementować własny używając tylko SDK (ucząc się przez działanie). Biorąc pod uwagę, że pamięć podręczna będzie używana w środowisku wielowątkowym, jakich struktur danych użyjesz? Zaimplementowałem już jeden przy użyciu LinkedHashMap i Collections # synchronizedMap , ale jestem ciekawy, czy któraś z nowych współbieżnych kolekcji byłaby lepszymi kandydatami.
AKTUALIZACJA: Właśnie czytałem najnowszą wersję Yegge, kiedy znalazłem ten samorodek:
Jeśli potrzebujesz stałego dostępu i chcesz utrzymać kolejność reklam, nie możesz zrobić nic lepszego niż LinkedHashMap, naprawdę wspaniała struktura danych. Jedynym sposobem, w jaki mogłoby być wspanialsze, jest istnienie równoległej wersji. Ale niestety.
Myślałam prawie dokładnie to samo, zanim poszłam z LinkedHashMap
+ Collections#synchronizedMap
wdrażania wspomniałem powyżej. Miło wiedzieć, że czegoś nie przeoczyłem.
Opierając się na dotychczasowych odpowiedziach, wydaje się, że najlepszym rozwiązaniem dla wysoce współbieżnego LRU byłoby rozszerzenie ConcurrentHashMap przy użyciu tej samej logiki, której LinkedHashMap
używa.
źródło
O(1)
wymagana wersja: stackoverflow.com/questions/23772102/…Odpowiedzi:
Podoba mi się wiele z tych sugestii, ale na razie myślę, że zostanę przy
LinkedHashMap
+Collections.synchronizedMap
. Jeśli wrócę do tego w przyszłości, prawdopodobnie popracuję nad rozszerzeniemConcurrentHashMap
w ten sam sposóbLinkedHashMap
rozszerzeńHashMap
.AKTUALIZACJA:
Na życzenie przedstawiam sedno mojej obecnej realizacji.
źródło
LinkedHashMap
wyraźnie popiera tę metodę tworzenia implementacji LRU.Gdybym robił to dzisiaj od zera, użyłbym guawy
CacheBuilder
.źródło
To jest runda druga.
Pierwsza runda była tym, co wymyśliłem, a potem ponownie przeczytałem komentarze z domeną nieco bardziej zakorzenioną w mojej głowie.
Oto najprostsza wersja z testem jednostkowym, który pokazuje, że działa w oparciu o inne wersje.
Najpierw wersja niewspółbieżna:
Prawdziwa flaga będzie śledzić dostęp do pobierania i wysyłania. Zobacz JavaDocs. RemoveEdelstEntry bez flagi true dla konstruktora po prostu zaimplementowałoby pamięć podręczną FIFO (zobacz uwagi poniżej na temat FIFO i removeEldestEntry).
Oto test, który dowodzi, że działa jako pamięć podręczna LRU:
Teraz dla wersji równoległej ...
pakiet org.boon.cache;
Możesz zobaczyć, dlaczego najpierw omawiam wersję, która nie jest współbieżna. Powyższe próby stworzenia pewnych pasków, aby zmniejszyć rywalizację o blokady. Więc haszuje klucz, a następnie wyszukuje ten skrót, aby znaleźć rzeczywistą pamięć podręczną. To sprawia, że rozmiar limitu jest bardziej sugestią / zgadywaniem z dużą ilością błędu, w zależności od tego, jak dobrze rozłożony jest algorytm skrótu kluczy.
Oto test pokazujący, że wersja równoległa prawdopodobnie działa. :) (Test pod ostrzałem byłby prawdziwym sposobem).
To jest ostatni post .. Pierwszy post, który usunąłem, ponieważ był to LFU, a nie pamięć podręczna LRU.
Pomyślałem, że spróbuję jeszcze raz. Próbowałem wymyślić najprostszą wersję pamięci podręcznej LRU przy użyciu standardowego JDK bez zbyt wielu implementacji.
Oto co wymyśliłem. Moja pierwsza próba była trochę katastrofą, ponieważ zaimplementowałem LFU zamiast i LRU, a potem dodałem do niego FIFO i obsługę LRU ... i zdałem sobie sprawę, że staje się potworem. Potem zacząłem rozmawiać z moim kumplem Johnem, który był ledwo zainteresowany, a potem szczegółowo opisałem, jak zaimplementowałem LFU, LRU i FIFO i jak można to zmienić za pomocą prostego argumentu ENUM, a potem zdałem sobie sprawę, że wszystko, czego naprawdę chcę był prostym LRU. Więc zignoruj wcześniejszy post ode mnie i daj mi znać, jeśli chcesz zobaczyć pamięć podręczną LRU / LFU / FIFO, którą można przełączać za pomocą wyliczenia ... nie? Ok .. oto on.
Najprostszy możliwy LRU wykorzystujący tylko JDK. Zaimplementowałem zarówno wersję współbieżną, jak i inną.
Stworzyłem wspólny interfejs (to minimalizm, więc prawdopodobnie brakuje kilku funkcji, które byś chciał, ale działa w moich przypadkach użycia, ale pozwól, jeśli chcesz zobaczyć funkcję XYZ, daj mi znać ... żyję, aby pisać kod.) .
Możesz się zastanawiać, czym jest getSilent . Używam tego do testów. getSilent nie zmienia wyniku LRU elementu.
Najpierw nierównoczesny ....
Queue.removeFirstOccurrence jest potencjalnie kosztowna operacja, jeśli masz dużą pamięć podręczną. Można wziąć jako przykład LinkedList i dodać mapę skrótów wyszukiwania wstecznego od elementu do węzła, aby operacje usuwania były DUŻO SZYBSZE i bardziej spójne. Ja też zacząłem, ale potem zdałem sobie sprawę, że tego nie potrzebuję. Ale może...
Po wywołaniu put klucz zostanie dodany do kolejki. Po wywołaniu get klucz jest usuwany i ponownie dodawany na początek kolejki.
Jeśli twoja skrzynka jest mała, a budowanie przedmiotu jest drogie, powinna to być dobra skrzynka. Jeśli twoja pamięć podręczna jest naprawdę duża, wyszukiwanie liniowe może być szyjką butelki, zwłaszcza jeśli nie masz gorących obszarów pamięci podręcznej. Im bardziej intensywne są gorące punkty, tym szybsze jest wyszukiwanie liniowe, ponieważ gorące elementy zawsze znajdują się na szczycie wyszukiwania liniowego. W każdym razie ... aby to działało szybciej, jest napisanie innej LinkedList, która ma operację usuwania, która ma odwrotne wyszukiwanie elementu do węzła w celu usunięcia, a następnie usuwanie byłoby tak szybkie, jak usunięcie klucza z mapy skrótów.
Jeśli masz pamięć podręczną poniżej 1000 elementów, powinno to działać dobrze.
Oto prosty test pokazujący jego działanie w akcji.
Ostatnia pamięć podręczna LRU była jednowątkowa i proszę nie pakować jej w nic zsynchronizowanego ....
Oto próba równoległej wersji.
Główne różnice to użycie ConcurrentHashMap zamiast HashMap oraz użycie Lock (mogłem uciec z synchronizacją, ale ...).
Nie testowałem go pod ostrzałem, ale wygląda na to, że jest to prosta pamięć podręczna LRU, która może się sprawdzić w 80% przypadków użycia, w których potrzebujesz prostej mapy LRU.
Czekam na opinie, z wyjątkiem tego, dlaczego nie używasz biblioteki a, b lub c. Powodem, dla którego nie zawsze używam biblioteki, jest to, że nie zawsze chcę, aby każdy plik wojenny miał 80 MB i piszę biblioteki, więc staram się tworzyć wtyczki bibliotek z wystarczająco dobrym rozwiązaniem i ktoś może podłączyć - u innego dostawcy pamięci podręcznej, jeśli chcą. :) Nigdy nie wiem, kiedy ktoś może potrzebować guawy, ehcache lub czegoś innego, czego nie chcę dołączać, ale jeśli stworzę wtyczkę do buforowania, też ich nie wykluczę.
Zmniejszenie zależności ma swoją nagrodę. Uwielbiam otrzymywać opinie na temat tego, jak to uprościć lub przyspieszyć, lub jedno i drugie.
Również jeśli ktoś wie, że jest gotowy do pracy ....
Ok .. Wiem, o czym myślisz ... Dlaczego po prostu nie użyje wpisu removeEldest z LinkedHashMap i cóż, powinienem, ale .... ale .. ale .. To byłoby FIFO, a nie LRU i byliśmy próbując wdrożyć LRU.
Ten test kończy się niepowodzeniem dla powyższego kodu ...
Oto szybka i brudna pamięć podręczna FIFO przy użyciu metody removeEldestEntry.
FIFO są szybkie. Żadnego szukania. Możesz ustawić FIFO przed LRU, a to całkiem nieźle poradzi sobie z większością gorących wpisów. Lepszy LRU będzie potrzebował tego odwróconego elementu do funkcji Node.
W każdym razie ... teraz, gdy napisałem jakiś kod, przejdę przez inne odpowiedzi i zobaczę, co przegapiłem ... gdy zeskanowałem je po raz pierwszy.
źródło
LinkedHashMap
jest O (1), ale wymaga synchronizacji. Nie ma potrzeby ponownego wynajdywania koła.2 opcje zwiększania współbieżności:
1. Tworzenie wielu
LinkedHashMap
i mieszania do nich na przykład:LinkedHashMap[4], index 0, 1, 2, 3
. Na klawiszu zróbkey%4
(lubbinary OR
włącz[key, 3]
), aby wybrać mapę, którą chcesz umieścić / pobrać / usunąć.2. Możesz zrobić „prawie” LRU, rozszerzając
ConcurrentHashMap
i mając połączoną strukturę przypominającą mapę skrótów w każdym z regionów wewnątrz niej. Blokowanie byłoby bardziej szczegółowe niżLinkedHashMap
synchronizacja. Na jednymput
lubputIfAbsent
tylko kłódce na początku i końcu listy jest potrzebny (na region). Podczas usuwania lub pobierania cały region musi zostać zablokowany. Jestem ciekawy, czy mogą tu pomóc jakieś powiązane listy Atomic - prawdopodobnie tak jest w przypadku głowy listy. Może po więcej.Struktura nie zachowa całkowitego porządku, ale tylko porządek na region. Tak długo, jak liczba wpisów jest znacznie większa niż liczba regionów, jest to wystarczające dla większości skrytek. Każdy region będzie musiał mieć własną liczbę wjazdów, która będzie używana zamiast globalnej liczby wyzwalającej eksmisję. Domyślna liczba regionów w a
ConcurrentHashMap
to 16, co jest wystarczające dla większości dzisiejszych serwerów.byłoby łatwiejsze do napisania i szybsze przy umiarkowanej współbieżności.
byłoby trudniejsze do napisania, ale znacznie lepsze skalowanie przy bardzo dużej współbieżności. Byłoby wolniejsze dla normalnego dostępu (tak samo, jak
ConcurrentHashMap
wolniejsze niż wHashMap
przypadku braku współbieżności)źródło
Istnieją dwie implementacje open source.
Apache Solr ma ConcurrentLRUCache: https://lucene.apache.org/solr/3_6_1/org/apache/solr/util/ConcurrentLRUCache.html
Istnieje projekt typu open source dla ConcurrentLinkedHashMap: http://code.google.com/p/concurrentlinkedhashmap/
źródło
ConcurrentLinkedHashMap
jest interesujące. Twierdzi, że został wtoczonyMapMaker
z guawy, ale nie zauważyłem tego w dokumentach. Masz jakiś pomysł, co się dzieje z tym wysiłkiem?Rozważałbym użycie java.util.concurrent.PriorityBlockingQueue , z priorytetem określanym przez licznik „numberOfUses” w każdym elemencie. Byłbym bardzo, bardzo ostrożny, aby uzyskać poprawną synchronizację, ponieważ licznik „numberOfUses” oznacza, że element nie może być niezmienny.
Obiekt element byłby opakowaniem dla obiektów w pamięci podręcznej:
źródło
Mam nadzieję że to pomoże .
źródło
Pamięć podręczną LRU można zaimplementować za pomocą ConcurrentLinkedQueue i ConcurrentHashMap, które mogą być również używane w scenariuszu wielowątkowym. Głową kolejki jest ten element, który najdłużej znajdował się w kolejce. Ogon kolejki to ten element, który był w kolejce najkrócej. Kiedy element istnieje w Mapie, możemy go usunąć z LinkedQueue i wstawić na końcu.
źródło
put
.Oto moja implementacja dla LRU. Użyłem PriorityQueue, który w zasadzie działa jako FIFO, a nie Threadafe. Użyty komparator oparty na czasie tworzenia strony i na podstawie kolejności stron według ostatniego używanego czasu.
Strony do rozważenia: 2, 1, 0, 2, 8, 2, 4
Strona dodana do cache to: 2
Strona dodana do cache to: 1
Strona dodana do cache to: 0
Strona: 2 już istnieje w cache. Czas ostatniego dostępu do aktualizacji
Błąd strony, STRONA: 1, Zastąpiona STRONA: 8
Strona dodana do pamięci podręcznej to: 8
Strona: 2 już istnieje w pamięci podręcznej. Czas ostatniego dostępu do aktualizacji
Usterka strony, STRONA: 0, Zastąpiona STRONA: 4
Strona dodana do pamięci podręcznej to: 4
WYNIK
Strony LRUCache
-------------
PageName: 8, PageCreationTime: 1365957019974
PageName: 2, PageCreationTime: 1365957020074
PageName: 4, PageCreationTime: 1365957020174
Wprowadź kod tutaj
źródło
Oto moja przetestowana, najlepiej działająca, współbieżna implementacja pamięci podręcznej LRU bez żadnego zsynchronizowanego bloku:
}
źródło
To jest pamięć podręczna LRU, której używam, która hermetyzuje LinkedHashMap i obsługuje współbieżność z prostą blokadą synchronizacji chroniącą soczyste miejsca. „Dotyka” elementów, gdy są używane, aby ponownie stały się „najświeższym” elementem, tak że jest to właściwie LRU. Miałem również wymóg, aby moje elementy miały minimalną żywotność, którą można również traktować jako dozwolony „maksymalny czas bezczynności”, wtedy jesteś gotowy do eksmisji.
Zgadzam się jednak z konkluzją Hanka i przyjąłem odpowiedź - gdybym zaczynał dzisiaj od nowa, sprawdziłbym guawy
CacheBuilder
.źródło
Cóż, jeśli chodzi o pamięć podręczną, generalnie będziesz wyszukiwać dane przez obiekt proxy (adres URL, ciąg ...), więc pod względem interfejsu będziesz potrzebować mapy. ale żeby pozbyć się rzeczy, potrzebujesz struktury takiej jak kolejka. Wewnętrznie utrzymywałbym dwie struktury danych, Priority-Queue i HashMap. Oto implementacja, która powinna być w stanie zrobić wszystko w czasie O (1).
Oto klasa, którą przygotowałem dość szybko:
Oto jak to działa. Klucze są przechowywane na połączonej liście, a najstarsze klucze znajdują się na początku listy (nowe klucze znajdują się z tyłu), więc kiedy chcesz coś `` wysunąć '', po prostu zdejmij to z przodu kolejki, a następnie użyj klucza, aby usuń wartość z mapy. Kiedy element zostaje przywołany, pobierasz ValueHolder z mapy, a następnie używasz zmiennej queuelocation, aby usunąć klucz z jego bieżącej lokalizacji w kolejce, a następnie umieszczasz go z tyłu kolejki (jest to teraz ostatnio używany). Dodawanie rzeczy przebiega prawie tak samo.
Jestem pewien, że jest tu mnóstwo błędów i nie zaimplementowałem żadnej synchronizacji. ale ta klasa zapewni O (1) dodawanie do pamięci podręcznej, O (1) usuwanie starych elementów i O (1) pobieranie elementów pamięci podręcznej. Nawet trywialna synchronizacja (po prostu zsynchronizuj każdą metodę publiczną) nadal będzie miała niewielką rywalizację o blokady ze względu na czas wykonywania. Jeśli ktoś ma sprytne sztuczki synchronizacyjne, byłbym bardzo zainteresowany. Jestem również pewien, że istnieją dodatkowe optymalizacje, które można zaimplementować za pomocą zmiennej maxsize w odniesieniu do mapy.
źródło
LinkedHashMap
+Collections.synchronizedMap()
?Spójrz na ConcurrentSkipListMap . Powinien dać ci log (n) czas na przetestowanie i usunięcie elementu, jeśli jest już zawarty w pamięci podręcznej, oraz stały czas na ponowne dodanie.
Potrzebowałbyś tylko licznika itp. I elementu opakowującego, aby wymusić zamówienie LRU i upewnić się, że ostatnie rzeczy zostaną odrzucone, gdy pamięć podręczna jest pełna.
źródło
ConcurrentSkipListMap
jakąś korzyść w zakresie łatwości wdrożeniaConcurrentHashMap
, czy jest to po prostu przypadek uniknięcia patologicznych przypadków?ConcurrentSkipListMap
implementacją utworzyłbym nową implementacjęMap
interfejsu, który delegujeConcurrentSkipListMap
i wykonuje pewnego rodzaju zawijanie, tak aby arbitralne typy kluczy były opakowane w typ, który można łatwo sortować na podstawie ostatniego dostępu?Oto moja krótka realizacja, skrytykuj ją lub popraw!
źródło
Oto moja własna implementacja tego problemu
simplelrucache zapewnia bezpieczne wątkowo, bardzo proste, nierozproszone buforowanie LRU z obsługą TTL. Zapewnia dwie implementacje:
Możesz go znaleźć tutaj: http://code.google.com/p/simplelrucache/
źródło
Najlepszym sposobem na osiągnięcie tego jest użycie LinkedHashMap, który utrzymuje kolejność wstawiania elementów. Oto przykładowy kod:
}
źródło
Szukam lepszej pamięci podręcznej LRU przy użyciu kodu Java. Czy jest możliwe udostępnianie kodu pamięci podręcznej Java LRU przy użyciu
LinkedHashMap
iCollections#synchronizedMap
? Obecnie używamLRUMap implements Map
i kod działa dobrze, ale przechodzęArrayIndexOutofBoundException
do testów obciążenia przy użyciu 500 użytkowników według poniższej metody. Metoda przenosi ostatni obiekt na początek kolejki.get(Object key)
aput(Object key, Object value)
metoda wywołuje powyższąmoveToFront
metodę.źródło
Chciałem dodać komentarz do odpowiedzi udzielonej przez Hanka ale trochę jak nie jestem w stanie - potraktuj to jako komentarz
LinkedHashMap utrzymuje również kolejność dostępu w oparciu o parametr przekazany w jego konstruktorze. Zachowuje podwójną listę w celu utrzymania porządku (patrz LinkedHashMap.Entry)
@Pacerier to prawda, że LinkedHashMap zachowuje tę samą kolejność podczas iteracji, jeśli element jest dodawany ponownie, ale to tylko w przypadku trybu zamówienia wstawiania.
to właśnie znalazłem w dokumentach java obiektu LinkedHashMap.Entry
ta metoda dba o przeniesienie ostatnio otwieranego elementu na koniec listy. Podsumowując, LinkedHashMap to najlepsza struktura danych do implementacji LRUCache.
źródło
Kolejna myśl, a nawet prosta implementacja z wykorzystaniem kolekcji Java LinkedHashMap.
LinkedHashMap udostępnił metodę removeEldestEntry, którą można przesłonić w sposób opisany w przykładzie. Domyślnie implementacja tej struktury kolekcji jest fałszywa. Jeśli jej prawda i rozmiar tej struktury przekracza początkową pojemność, najstarsze lub starsze elementy zostaną usunięte.
Możemy mieć pageno i zawartość strony w moim przypadku pageno to liczba całkowita, a zawartość strony zachowałem ciąg wartości numeru strony.
Wynik wykonania powyższego kodu jest następujący:
źródło
Zgodnie z koncepcją @sanjanab (ale po poprawkach) wykonałem swoją wersję LRUCache, zapewniając również Konsumenta, który w razie potrzeby pozwala zrobić coś z usuniętymi elementami.
źródło
Android oferuje implementację pamięci podręcznej LRU . Kod jest czysty i prosty.
źródło