Skip to content

Commit

Permalink
Merge pull request #95 from k-r-g/cacheGroupOrdering
Browse files Browse the repository at this point in the history
Optionally support disabling maintaining a sorted list in CacheGroup
  • Loading branch information
msmith-techempower authored Jun 25, 2020
2 parents 34798e8 + 8894cfb commit 8483cc1
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 49 deletions.
163 changes: 116 additions & 47 deletions gemini/src/main/java/com/techempower/cache/CacheGroup.java
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,11 @@ public void setObjects(Collection<T> objects)

// Replace the member variables.
this.objects = workMap;
this.objectsInOrder = new CopyOnWriteArrayList<>(workList);
// Avoid maintaining the sorted list if not needed.
if (comparator() != EntityGroup.NO_COMPARATOR)
{
this.objectsInOrder = new CopyOnWriteArrayList<>(workList);
}

// If we're setting objects from somewhere else, we should assume
// that this is initializing the cache group.
Expand Down Expand Up @@ -213,7 +217,11 @@ protected T getRaw(long id)
public List<T> list()
{
initializeIfNecessary();

// If sorting is disabled, return values().
if (comparator() == EntityGroup.NO_COMPARATOR)
{
return new ArrayList<>(this.objects.values());
}
return new ArrayList<>(this.objectsInOrder);
}

Expand Down Expand Up @@ -384,42 +392,66 @@ public final void addToCache(T... objectsToAdd)
{
// Grab references.
final ConcurrentMap<Long, T> map = this.objects;
final List<T> orderedList = this.objectsInOrder;
final Comparator<? super T> comparator = comparator();

synchronized (this)
// Avoid maintaining the sorted list if not needed.
if (comparator == EntityGroup.NO_COMPARATOR)
{
for (T object : objectsToAdd)
{
// Only proceed if we don't already have this reference in the cache.
if (!map.containsValue(object))
map.put(object.getId(), object);

if (areHighLowIdentitiesInitialized())
{
// If we already have a reference with the same ID, let's remove it.
if (map.containsKey(object.getId()))
if (object.getId() < this.lowestIdentity)
{
// Remove the existing reference from the ordered list.
orderedList.remove(map.get(object.getId()));
this.lowestIdentity = object.getId();
}

map.put(object.getId(), object);
int search = Collections.binarySearch(orderedList, object, comparator());
if (search < 0)
if (object.getId() > this.highestIdentity)
{
orderedList.add(-search - 1, object);
}
else
{
orderedList.add(object);
this.highestIdentity = object.getId();
}

if (areHighLowIdentitiesInitialized())
}
}
}
else
{
final List<T> orderedList = this.objectsInOrder;
synchronized (this)
{
for (T object : objectsToAdd)
{
// Only proceed if we don't already have this reference in the cache.
if (!map.containsValue(object))
{
if (object.getId() < this.lowestIdentity)
// If we already have a reference with the same ID, let's remove it.
if (map.containsKey(object.getId()))
{
// Remove the existing reference from the ordered list.
orderedList.remove(map.get(object.getId()));
}

map.put(object.getId(), object);
int search = Collections.binarySearch(orderedList, object, comparator);
if (search < 0)
{
this.lowestIdentity = object.getId();
orderedList.add(-search - 1, object);
}
if (object.getId() > this.highestIdentity)
else
{
this.highestIdentity = object.getId();
orderedList.add(object);
}

if (areHighLowIdentitiesInitialized())
{
if (object.getId() < this.lowestIdentity)
{
this.lowestIdentity = object.getId();
}
if (object.getId() > this.highestIdentity)
{
this.highestIdentity = object.getId();
}
}
}
}
Expand All @@ -442,8 +474,19 @@ public boolean removeFromCache(long... ids)
{
// Grab references.
final ConcurrentMap<Long, T> map = this.objects;

// Skip updating objectsInOrder if not using sorting.
if (comparator() == EntityGroup.NO_COMPARATOR)
{
for (long id : ids)
{
map.remove(id);
}
return true;
}

// Using sorting, so maintain objectsInOrder.
final List<T> orderedList = this.objectsInOrder;

synchronized (this)
{
for (long id : ids)
Expand Down Expand Up @@ -546,8 +589,13 @@ public void initialize()
{
synchronized (this)
{
this.objectsInOrder = new CopyOnWriteArrayList<>(fetchAllPersistedObjects());
copyOrderedObjectsToMap();
List<T> allObjects = fetchAllPersistedObjects();
// Avoid maintaining the sorted list if not needed.
if (comparator() != EntityGroup.NO_COMPARATOR)
{
this.objectsInOrder = new CopyOnWriteArrayList<>(allObjects);
}
copyListToObjectMap(allObjects);

// Reset the high and low identities.
resetHighLowIdentities();
Expand Down Expand Up @@ -582,13 +630,13 @@ protected void customPostInitialization()
/**
* Copies ordered objects to the LongMap.
*/
protected void copyOrderedObjectsToMap()
protected void copyListToObjectMap(List<T> l)
{
final Iterator<T> iter = this.objectsInOrder.iterator();
final Iterator<T> iter = l.iterator();

// Create a new map, work, to populate.
final ConcurrentMap<Long, T> work = new ConcurrentHashMap<>(
this.objectsInOrder.size());
l.size());
T co;
while (iter.hasNext())
{
Expand Down Expand Up @@ -661,7 +709,9 @@ protected boolean areHighLowIdentitiesInitialized()
*/
protected void calculateHighLowIdentities()
{
final Iterator<?> iter = this.objectsInOrder.iterator();
// Use objects instead of objectsInOrder in case NO_COMPARATOR is specified and
// objectsInOrder is not maintained.
final Iterator<?> iter = this.objects.values().iterator();

Identifiable co;
long id;
Expand Down Expand Up @@ -728,40 +778,53 @@ public int size()
@Override
public void refresh(long... ids)
{
// If the group is not initialized, we're done.
if (!this.initialized)
{
return;
}

// Grab references.
final ConcurrentMap<Long, T> map = this.objects;
final List<T> orderedList = this.objectsInOrder;
final Comparator<? super T> comparator = comparator();

synchronized (this)
{
// If the group is not initialized, we're done.
if (!this.initialized)
{
return;
}

// Fetch the new objects.
final TLongObjectMap<T> objectsMap = super.map(CollectionHelper.toList(ids));

for (long id : ids)
{
// Remove the object with this id from the cache, if it's there.
orderedList.remove(map.remove(id));
if (comparator == EntityGroup.NO_COMPARATOR)
{
map.remove(id);
}
else
{
// Only update orderedList if sorting is desired.
orderedList.remove(map.remove(id));
}
final T object = objectsMap.get(id);

// Put the newly loaded object into the cache.
if (object != null)
{
map.put(id, object);
// Use the comparator to insert it at the appropriate position.
int search = Collections.binarySearch(orderedList, object, comparator());
if (search < 0)
{
orderedList.add(-search - 1, object);
}
else
// Only update orderedList if sorting is desired.
if (comparator != EntityGroup.NO_COMPARATOR)
{
orderedList.add(object);
// Use the comparator to insert it at the appropriate position.
int search = Collections.binarySearch(orderedList, object, comparator);
if (search < 0)
{
orderedList.add(-search - 1, object);
}
else
{
orderedList.add(object);
}
}
}
}
Expand All @@ -777,6 +840,12 @@ public void refresh(long... ids)
@Override
public void reorder(long... ids)
{
if (comparator() == EntityGroup.NO_COMPARATOR)
{
// Nothing to do.
return;
}

// Grab references.
final ConcurrentMap<Long, T> map = this.objects;
final List<T> orderedList = this.objectsInOrder;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,11 @@ protected List<T> fetchAllPersistedObjects()
// We sort whatever the initializer gives us because there is no
// guarantee that it's in the right order.
List<T> result = initializer.list();
Collections.sort(result, comparator());
// Skip sorting if not desired.
if (comparator() != EntityGroup.NO_COMPARATOR)
{
Collections.sort(result, comparator());
}
return result;
}

Expand Down
21 changes: 20 additions & 1 deletion gemini/src/main/java/com/techempower/data/EntityGroup.java
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,21 @@ public int compare(Identifiable o1, Identifiable o2)
}
};

/**
* Skips sorting entirely. This is the comparator to specify if your application
* prefers to avoid the performance cost (both in synchronization locks and data
* structure updates) of maintaining a sorted list of these entities.
*/
public static final Comparator<Identifiable> NO_COMPARATOR =
new Comparator<Identifiable>() {
@Override
public int compare(Identifiable o1, Identifiable o2)
{
// This should never be called.
throw new UnsupportedOperationException();
}
};

/**
* Gets a suitable default Comparator for the group, using the natural order
* if the type is Comparable, and the IDs if not.
Expand Down Expand Up @@ -702,7 +717,11 @@ protected List<T> rawList()
{
throw new EntityException("Exception during SELECT (list).", e);
}
Collections.sort(objects, this.comparator);
// Skip sorting if not desired.
if (this.comparator != NO_COMPARATOR)
{
Collections.sort(objects, this.comparator);
}
return objects;
}

Expand Down

0 comments on commit 8483cc1

Please sign in to comment.