Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ChunkedAssociativeLongArray re-use expired nodes #1145

Merged
merged 2 commits into from
Jun 23, 2017
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use multi-line comments with an asterix on every line. We are trying to follow the Google Java Style Guide. See section about comments.

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,34 @@ class ChunkedAssociativeLongArray {
this.defaultChunkSize = chunkSize;
}

private Chunk allocateChunk() {
SoftReference<Chunk> chunkRef = chunksCache.pollLast();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about simplifying this method to:

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;
  }
}

while (chunkRef != null && chunkRef.get() == null) {
chunkRef = chunksCache.pollLast();
}

Chunk chunk = chunkRef == null ? null : chunkRef.get();
if (chunk == null) {
chunk = new Chunk(this.defaultChunkSize);
} else {
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 +79,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 +149,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 +164,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 +208,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