88 *
99 * SPDX-License-Identifier: BSD-3-Clause
1010 *******************************************************************************/
11+ // Some portions generated by Codex
1112package org .eclipse .rdf4j .sail .lmdb ;
1213
13- import java .util .Iterator ;
14- import java .util .concurrent . ConcurrentHashMap ;
14+ import java .util .Arrays ;
15+ import java .util .Objects ;
1516
1617/**
17- * Limited-size concurrent cache. The actual cleanup to keep the size limited is done once per
18- * <code>CLEANUP_INTERVAL</code> invocations of the protected method <code>cleanUp</code>. <code>cleanUp</code> method
19- * is called every time by <code>put</code> The maximum size is maintained approximately. Cleanup is not done if size is
20- * less than <code>capacity + CLEANUP_INTERVAL / 2</code>.
18+ * Fixed-size concurrent cache with approximate FIFO eviction per hash set. The cache never grows beyond its slot
19+ * budget; capacity is rounded up to a power-of-two number of slots and entries are evicted in O(1).
2120 */
2221public class ConcurrentCache <K , V > {
2322
24- private static final int CLEANUP_INTERVAL = 1024 ;
23+ private static final int WAYS = 4 ;
2524
26- private static final float LOAD_FACTOR = 0.75f ;
25+ private final Entry <K , V >[] entries ;
26+ private final int [] nextVictim ;
27+ private final int setMask ;
28+ private final int setShift ;
2729
28- private final int capacity ;
29-
30- private volatile int cleanupTick = 0 ;
31-
32- protected final ConcurrentHashMap <K , V > cache ;
30+ private volatile long generation = 1 ;
3331
32+ @ SuppressWarnings ("unchecked" )
3433 public ConcurrentCache (int capacity ) {
35- this .capacity = capacity ;
36- this .cache = new ConcurrentHashMap <>((int ) (capacity / LOAD_FACTOR ), LOAD_FACTOR );
34+ if (capacity <= 0 ) {
35+ entries = (Entry <K , V >[]) new Entry [0 ];
36+ nextVictim = new int [0 ];
37+ setMask = 0 ;
38+ setShift = 0 ;
39+ return ;
40+ }
41+
42+ int slotCount = nextPowerOfTwo (Math .max (WAYS , capacity ));
43+ int setCount = slotCount / WAYS ;
44+ entries = (Entry <K , V >[]) new Entry [slotCount ];
45+ nextVictim = new int [setCount ];
46+ setMask = setCount - 1 ;
47+ setShift = Integer .numberOfTrailingZeros (setCount );
3748 }
3849
3950 public V get (Object key ) {
40- return cache .get (key );
51+ Objects .requireNonNull (key );
52+ if (entries .length == 0 ) {
53+ return null ;
54+ }
55+
56+ int hash = spread (key .hashCode ());
57+ int base = setBase (hash );
58+ int preferredOffset = preferredOffset (hash );
59+ long currentGeneration = generation ;
60+
61+ for (int i = 0 ; i < WAYS ; i ++) {
62+ Entry <K , V > entry = entries [base + ((preferredOffset + i ) & (WAYS - 1 ))];
63+ if (entry != null && entry .hash == hash && entry .generation == currentGeneration
64+ && sameKey (key , entry .key )) {
65+ return entry .value ;
66+ }
67+ }
68+ return null ;
4169 }
4270
4371 public V put (K key , V value ) {
44- cleanUp ();
45- return cache .put (key , value );
72+ Objects .requireNonNull (key );
73+ Objects .requireNonNull (value );
74+ if (entries .length == 0 ) {
75+ return null ;
76+ }
77+
78+ int hash = spread (key .hashCode ());
79+ int setIndex = hash & setMask ;
80+ int base = setIndex * WAYS ;
81+ int preferredOffset = preferredOffset (hash );
82+ long currentGeneration = generation ;
83+ int emptySlot = -1 ;
84+
85+ for (int i = 0 ; i < WAYS ; i ++) {
86+ int slot = base + ((preferredOffset + i ) & (WAYS - 1 ));
87+ Entry <K , V > entry = entries [slot ];
88+ if (entry == null ) {
89+ if (emptySlot < 0 ) {
90+ emptySlot = slot ;
91+ }
92+ continue ;
93+ }
94+
95+ if (entry .hash == hash && entry .generation == currentGeneration && sameKey (key , entry .key )) {
96+ if (entry .value != value ) {
97+ entries [slot ] = new Entry <>(key , value , hash , currentGeneration );
98+ }
99+ return entry .value ;
100+ }
101+ }
102+
103+ if (emptySlot >= 0 ) {
104+ entries [emptySlot ] = new Entry <>(key , value , hash , currentGeneration );
105+ return null ;
106+ }
107+
108+ int firstVictim = nextVictim [setIndex ] & (WAYS - 1 );
109+ for (int i = 0 ; i < WAYS ; i ++) {
110+ int victimOffset = (firstVictim + i ) & (WAYS - 1 );
111+ int slot = base + victimOffset ;
112+ Entry <K , V > victim = entries [slot ];
113+ if (victim == null || victim .generation != currentGeneration || onEntryRemoval (victim .key )) {
114+ entries [slot ] = new Entry <>(key , value , hash , currentGeneration );
115+ nextVictim [setIndex ] = (victimOffset + 1 ) & (WAYS - 1 );
116+ return null ;
117+ }
118+ }
119+
120+ return null ;
46121 }
47122
48123 public void clear () {
49- cache .clear ();
124+ long nextGeneration = generation + 1 ;
125+ generation = nextGeneration == 0 ? 1 : nextGeneration ;
126+ Arrays .fill (entries , null );
127+ Arrays .fill (nextVictim , 0 );
50128 }
51129
52130 /**
@@ -58,35 +136,38 @@ protected boolean onEntryRemoval(K key) {
58136 return true ;
59137 }
60138
61- protected void cleanUp () {
62- // This is not thread-safe, but the worst that can happen is that we may (rarely) get slightly longer
63- // cleanup intervals or run cleanUp twice
64- cleanupTick ++;
65- if (cleanupTick <= CLEANUP_INTERVAL ) {
66- return ;
67- }
68-
69- cleanupTick %= CLEANUP_INTERVAL ;
139+ private int setBase (int hash ) {
140+ return (hash & setMask ) * WAYS ;
141+ }
70142
71- synchronized (cache ) {
143+ private int preferredOffset (int hash ) {
144+ return (hash >>> setShift ) & (WAYS - 1 );
145+ }
72146
73- final int size = cache .size ();
74- if (size < capacity + CLEANUP_INTERVAL / 2 ) {
75- return ;
76- }
147+ private static int spread (int hash ) {
148+ return hash ^ (hash >>> 16 );
149+ }
77150
78- Iterator <K > iter = cache .keySet ().iterator ();
151+ private static boolean sameKey (Object key , Object entryKey ) {
152+ return key == entryKey || key .equals (entryKey );
153+ }
79154
80- float removeEachTh = (float ) size / (size - capacity );
155+ private static int nextPowerOfTwo (int n ) {
156+ return 1 << (Integer .SIZE - Integer .numberOfLeadingZeros (n - 1 ));
157+ }
81158
82- for ( int i = 0 ; iter . hasNext (); i ++) {
159+ private static final class Entry < K , V > {
83160
84- K key = iter .next ();
161+ private final K key ;
162+ private final V value ;
163+ private final int hash ;
164+ private final long generation ;
85165
86- if (i % removeEachTh < 1 ) {
87- cache .computeIfPresent (key , (k , v ) -> onEntryRemoval (k ) ? null : v );
88- }
89- }
166+ private Entry (K key , V value , int hash , long generation ) {
167+ this .key = key ;
168+ this .value = value ;
169+ this .hash = hash ;
170+ this .generation = generation ;
90171 }
91172 }
92173}
0 commit comments