Skip to content

Commit

Permalink
Adapt some tests to the new API, tune the ObjectPoolingAllocator.
Browse files Browse the repository at this point in the history
  • Loading branch information
axel22 committed Feb 6, 2023
1 parent be44f6a commit 44c3813
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,59 +48,111 @@

public class LockFreePrefixTreeTest {

private static final int RETRY_DELAY_MILLIS = 160;

@Test
public void smallAlphabet() {
public void smallAlphabetHeap() {
LockFreePrefixTree.HeapAllocator a = new LockFreePrefixTree.HeapAllocator();
LockFreePrefixTree tree = new LockFreePrefixTree(a);
smallAlphabet(a);
}

tree.root().at(a, 2L).at(a, 12L).at(a, 18L).setValue(42);
tree.root().at(a, 2L).at(a, 12L).at(a, 19L).setValue(43);
tree.root().at(a, 2L).at(a, 12L).at(a, 20L).setValue(44);
@Test
public void smallAlphabetPool() {
LockFreePrefixTree.ObjectPoolingAllocator a = new LockFreePrefixTree.ObjectPoolingAllocator();
smallAlphabet(a);
}

Assert.assertEquals(42, tree.root().at(a, 2L).at(a, 12L).at(a, 18L).value());
Assert.assertEquals(43, tree.root().at(a, 2L).at(a, 12L).at(a, 19L).value());
Assert.assertEquals(44, tree.root().at(a, 2L).at(a, 12L).at(a, 20L).value());
private static void smallAlphabet(LockFreePrefixTree.Allocator a) {
try {
LockFreePrefixTree tree = new LockFreePrefixTree(a);

tree.root().at(a, 3L).at(a, 19L).setValue(21);
tree.root().at(a, 2L).at(a, 12L).at(a, 18L).setValue(42);
tree.root().at(a, 2L).at(a, 12L).at(a, 19L).setValue(43);
tree.root().at(a, 2L).at(a, 12L).at(a, 20L).setValue(44);

Assert.assertEquals(42, tree.root().at(a, 2L).at(a, 12L).at(a, 18L).value());
Assert.assertEquals(21, tree.root().at(a, 3L).at(a, 19L).value());
Assert.assertEquals(42, tree.root().at(a, 2L).at(a, 12L).at(a, 18L).value());
Assert.assertEquals(43, tree.root().at(a, 2L).at(a, 12L).at(a, 19L).value());
Assert.assertEquals(44, tree.root().at(a, 2L).at(a, 12L).at(a, 20L).value());

tree.root().at(a, 2L).at(a, 6L).at(a, 11L).setValue(123);
tree.root().at(a, 3L).at(a, 19L).setValue(21);

Assert.assertEquals(123, tree.root().at(a, 2L).at(a, 6L).at(a, 11L).value());
Assert.assertEquals(42, tree.root().at(a, 2L).at(a, 12L).at(a, 18L).value());
Assert.assertEquals(21, tree.root().at(a, 3L).at(a, 19L).value());

tree.root().at(a, 3L).at(a, 19L).at(a, 11L).incValue();
tree.root().at(a, 3L).at(a, 19L).at(a, 11L).incValue();
tree.root().at(a, 2L).at(a, 6L).at(a, 11L).setValue(123);

Assert.assertEquals(2, tree.root().at(a, 3L).at(a, 19L).at(a, 11L).value());
Assert.assertEquals(123, tree.root().at(a, 2L).at(a, 6L).at(a, 11L).value());

for (long i = 1L; i < 6L; i++) {
tree.root().at(a, 1L).at(a, 2L).at(a, i).setValue(i * 10);
}
for (long i = 1L; i < 6L; i++) {
Assert.assertEquals(i * 10, tree.root().at(a, 1L).at(a, 2L).at(a, i).value());
tree.root().at(a, 3L).at(a, 19L).at(a, 11L).incValue();
tree.root().at(a, 3L).at(a, 19L).at(a, 11L).incValue();

Assert.assertEquals(2, tree.root().at(a, 3L).at(a, 19L).at(a, 11L).value());

for (long i = 1L; i < 6L; i++) {
tree.root().at(a, 1L).at(a, 2L).at(a, i).setValue(i * 10);
}
for (long i = 1L; i < 6L; i++) {
Assert.assertEquals(i * 10, tree.root().at(a, 1L).at(a, 2L).at(a, i).value());
}
} finally {
a.shutdown();
}
}

@Test
public void largeAlphabet() {
public void largeAlphabetHeap() {
LockFreePrefixTree.HeapAllocator a = new LockFreePrefixTree.HeapAllocator();
LockFreePrefixTree tree = new LockFreePrefixTree(a);
try {
largeAlphabet(tree, a);
} finally {
a.shutdown();
}
}

@Test
public void largeAlphabetPool() throws InterruptedException {
LockFreePrefixTree.ObjectPoolingAllocator a = new LockFreePrefixTree.ObjectPoolingAllocator();
LockFreePrefixTree tree = new LockFreePrefixTree(a);
try {
while (!largeAlphabet(tree, a)) {
synchronized (a) {
a.wait(RETRY_DELAY_MILLIS);
}
}
} finally {
a.shutdown();
}
}

private static boolean largeAlphabet(LockFreePrefixTree tree, LockFreePrefixTree.Allocator a) {
for (long i = 1L; i < 128L; i++) {
LockFreePrefixTree.Node first = tree.root().at(a, i);
if (first == null) {
return false;
}
for (long j = 1L; j < 64L; j++) {
LockFreePrefixTree.Node second = first.at(a, j);
if (second == null) {
return false;
}
second.setValue(i * j);
}
}
for (long i = 1L; i < 128L; i++) {
LockFreePrefixTree.Node first = tree.root().at(a, i);
if (first == null) {
return false;
}
for (long j = 1L; j < 64L; j++) {
LockFreePrefixTree.Node second = first.at(a, j);
if (second == null) {
return false;
}
Assert.assertEquals(i * j, second.value());
}
}
return true;
}

private static void inParallel(int parallelism, Consumer<Integer> body) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,9 +185,12 @@ public long incValue() {

/**
* Get existing (or create if missing) child with the given key.
*
*
* May return {@code null} if the operation cannot complete, for example, due to inability
* to allocate nodes.
*
* @param childKey the key of the child.
* @return The child with the given childKey.
* @return The child with the given childKey, or {@code null} if cannot complete.
* @since 22.3
*/
@SuppressWarnings("unchecked")
Expand Down Expand Up @@ -435,6 +438,8 @@ public <C> void topDown(C initialContext, BiFunction<C, Long, C> createContext,
* Exception that denotes that an allocation failed.
*/
public static class FailedAllocationException extends RuntimeException {
private static final long serialVersionUID = -1L;

@Override
public synchronized Throwable fillInStackTrace() {
return this;
Expand Down Expand Up @@ -510,9 +515,9 @@ public static class ObjectPoolingAllocator extends Allocator {
private static final int DEFAULT_HOUSEKEEPING_PERIOD_MILLIS = 72;
private static final int CHILDREN_POOL_COUNT = 32;
private static final int INITIAL_NODE_PREALLOCATION_COUNT = 2048;
private static final int INITIAL_LINEAR_CHILDREN_PREALLOCATION_COUNT = 512;
private static final int INITIAL_HASH_CHILDREN_PREALLOCATION_COUNT = 64;
private static final int EXPECTED_MAX_HASH_NODE_SIZE = 256;
private static final int INITIAL_LINEAR_CHILDREN_PREALLOCATION_COUNT = 2048;
private static final int INITIAL_HASH_CHILDREN_PREALLOCATION_COUNT = 128;
private static final int EXPECTED_MAX_HASH_NODE_SIZE = 512;
private static final int PREALLOCATION_MULTIPLIER = 2;

private final LockFreePool<Node> nodePool;
Expand All @@ -535,6 +540,7 @@ public ObjectPoolingAllocator(int housekeepingPeriodMillis) {
this.missedLinearChildrenRequestCounts = new AtomicIntegerArray(32);
this.missedHashChildrenRequestCounts = new AtomicIntegerArray(32);
this.housekeepingThread = new HousekeepingThread(housekeepingPeriodMillis);
this.housekeepingThread.start();
}

private static LockFreePool<Node> createNodePool() {
Expand All @@ -545,9 +551,9 @@ private static LockFreePool<Node> createNodePool() {
return pool;
}

@SuppressWarnings("unchecked")
@SuppressWarnings({"unchecked", "rawtypes"})
private static LockFreePool<Node.LinearChildren>[] createLinearChildrenPool() {
LockFreePool<Node.LinearChildren>[] pools = new LockFreePool<>[CHILDREN_POOL_COUNT];
LockFreePool<Node.LinearChildren>[] pools = new LockFreePool[CHILDREN_POOL_COUNT];
for (int sizeClass = 0; sizeClass < pools.length; sizeClass++) {
pools[sizeClass] = new LockFreePool<>();
if (sizeClass >= numberOfTrailingZeros(Node.INITIAL_LINEAR_NODE_SIZE) && sizeClass <= numberOfTrailingZeros(Node.MAX_LINEAR_NODE_SIZE)) {
Expand All @@ -560,9 +566,9 @@ private static LockFreePool<Node.LinearChildren>[] createLinearChildrenPool() {
return pools;
}

@SuppressWarnings("unchecked")
@SuppressWarnings({"unchecked", "rawtypes"})
private static LockFreePool<Node.HashChildren>[] createHashChildrenPool() {
LockFreePool<Node.HashChildren>[] pools = new LockFreePool<>[CHILDREN_POOL_COUNT];
LockFreePool<Node.HashChildren>[] pools = new LockFreePool[CHILDREN_POOL_COUNT];
for (int sizeClass = 0; sizeClass < pools.length; sizeClass++) {
pools[sizeClass] = new LockFreePool<>();
if (sizeClass >= numberOfTrailingZeros(Node.INITIAL_HASH_NODE_SIZE) && sizeClass <= numberOfTrailingZeros(EXPECTED_MAX_HASH_NODE_SIZE)) {
Expand Down Expand Up @@ -642,15 +648,17 @@ private class HousekeepingThread extends Thread {
private void housekeep() {
int count = missedNodePoolRequestCount.get();
if (count > 0) {
for (int i = 0; i < Math.max(INITIAL_NODE_PREALLOCATION_COUNT, count * PREALLOCATION_MULTIPLIER); i++) {
int growthEstimate = Math.max(INITIAL_NODE_PREALLOCATION_COUNT, count * PREALLOCATION_MULTIPLIER);
for (int i = 0; i < growthEstimate; i++) {
nodePool.add(new Node());
}
missedNodePoolRequestCount.set(0);
}
for (int sizeClass = 0; sizeClass < linearChildrenPool.length; sizeClass++) {
count = missedLinearChildrenRequestCounts.get(sizeClass);
if (count > 0) {
for (int i = 0; i < Math.max(INITIAL_LINEAR_CHILDREN_PREALLOCATION_COUNT, count * PREALLOCATION_MULTIPLIER); i++) {
int growthEstimate = Math.max(INITIAL_LINEAR_CHILDREN_PREALLOCATION_COUNT, count * PREALLOCATION_MULTIPLIER);
for (int i = 0; i < growthEstimate; i++) {
linearChildrenPool[sizeClass].add(new Node.LinearChildren(1 << sizeClass));
}
missedLinearChildrenRequestCounts.set(sizeClass, 0);
Expand All @@ -659,7 +667,14 @@ private void housekeep() {
for (int sizeClass = 0; sizeClass < hashChildrenPool.length; sizeClass++) {
count = missedHashChildrenRequestCounts.get(sizeClass);
if (count > 0) {
for (int i = 0; i < Math.max(INITIAL_HASH_CHILDREN_PREALLOCATION_COUNT, count); i++) {
int growthEstimate;
if (sizeClass < Integer.numberOfTrailingZeros(EXPECTED_MAX_HASH_NODE_SIZE)) {
growthEstimate = Math.max(INITIAL_HASH_CHILDREN_PREALLOCATION_COUNT, count);
} else {
// We expect that the case of allocating very wide nodes is rare.
growthEstimate = count;
}
for (int i = 0; i < growthEstimate; i++) {
hashChildrenPool[sizeClass].add(new Node.HashChildren(1 << sizeClass));
}
missedHashChildrenRequestCounts.set(sizeClass, 0);
Expand Down

0 comments on commit 44c3813

Please sign in to comment.