Skip to content

Commit

Permalink
ChunkedAssociativeLongArray re-use expired nodes (#1145)
Browse files Browse the repository at this point in the history
* Chunks cache implementation

* Cleanup after code review.
  • Loading branch information
storozhukBM authored and arteam committed Jun 23, 2017
1 parent b01f7cd commit 2d43206
Showing 1 changed file with 48 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,36 @@
import static java.lang.System.arraycopy;
import static java.util.Arrays.binarySearch;

import java.lang.ref.SoftReference;
import java.util.ArrayDeque;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.ListIterator;

class ChunkedAssociativeLongArray {
private static final long[] EMPTY = new long[0];
private static final int DEFAULT_CHUNK_SIZE = 512;
private static final int MAX_CACHE_SIZE = 128;

private final int defaultChunkSize;
/*
* We use this ArrayDeque as cache to store chunks that are expired and removed from main data structure.
* Then instead of allocating new Chunk immediately we are trying to poll one from this deque.
* So if you have constant or slowly changing load ChunkedAssociativeLongArray will never
* throw away old chunks or allocate new ones which makes this data structure almost garbage free.
*/
private final ArrayDeque<SoftReference<Chunk>> chunksCache = new ArrayDeque<SoftReference<Chunk>>();

/*
* Why LinkedList if we are creating fast data structure with low GC overhead?
*
* First of all LinkedList here has relatively small size countOfStoredMeasurements / DEFAULT_CHUNK_SIZE.
* And we are heavily rely on LinkedList implementation because:
* 1. Now we deleting chunks from both sides of the list in trim(long startKey, long endKey)
* 2. Deleting from and inserting chunks into the middle in clear(long startKey, long endKey)
*
* LinkedList gives us O(1) complexity for all this operations and that is not the case with ArrayList.
*/
private final LinkedList<Chunk> chunks = new LinkedList<Chunk>();

ChunkedAssociativeLongArray() {
Expand All @@ -22,11 +43,33 @@ class ChunkedAssociativeLongArray {
this.defaultChunkSize = chunkSize;
}

private Chunk allocateChunk() {
while (true) {
final SoftReference<Chunk> chunkRef = chunksCache.pollLast();
if (chunkRef == null) {
return new Chunk(defaultChunkSize);
}
final Chunk chunk = chunkRef.get();
if (chunk != null) {
chunk.cursor = 0;
chunk.startIndex = 0;
chunk.chunkSize = chunk.keys.length;
return chunk;
}
}
}

private void freeChunk(Chunk chunk) {
if (chunksCache.size() < MAX_CACHE_SIZE) {
chunksCache.add(new SoftReference<Chunk>(chunk));
}
}

synchronized boolean put(long key, long value) {
Chunk activeChunk = chunks.peekLast();

if (activeChunk == null) { // lazy chunk creation
activeChunk = new Chunk(this.defaultChunkSize);
activeChunk = allocateChunk();
chunks.add(activeChunk);

} else {
Expand All @@ -35,7 +78,7 @@ synchronized boolean put(long key, long value) {
}
boolean isFull = activeChunk.cursor - activeChunk.startIndex == activeChunk.chunkSize;
if (isFull) {
activeChunk = new Chunk(this.defaultChunkSize);
activeChunk = allocateChunk();
chunks.add(activeChunk);
}
}
Expand Down Expand Up @@ -105,6 +148,7 @@ synchronized void trim(long startKey, long endKey) {
while (fromHeadIterator.hasPrevious()) {
Chunk currentHead = fromHeadIterator.previous();
if (isFirstElementIsEmptyOrGreaterEqualThanKey(currentHead, endKey)) {
freeChunk(currentHead);
fromHeadIterator.remove();
} else {
int newEndIndex = findFirstIndexOfGreaterEqualElements(
Expand All @@ -119,6 +163,7 @@ synchronized void trim(long startKey, long endKey) {
while (fromTailIterator.hasNext()) {
Chunk currentTail = fromTailIterator.next();
if (isLastElementIsLessThanKey(currentTail, startKey)) {
freeChunk(currentTail);
fromTailIterator.remove();
} else {
int newStartIndex = findFirstIndexOfGreaterEqualElements(
Expand Down Expand Up @@ -162,6 +207,7 @@ synchronized void clear(long startKey, long endKey) {
while (fromHeadIterator.hasPrevious()) {
Chunk afterGapHead = fromHeadIterator.previous();
if (isFirstElementIsEmptyOrGreaterEqualThanKey(afterGapHead, startKey)) {
freeChunk(afterGapHead);
fromHeadIterator.remove();
} else {
int newEndIndex = findFirstIndexOfGreaterEqualElements(
Expand Down

0 comments on commit 2d43206

Please sign in to comment.