Skip to content

Commit

Permalink
Gattai!
Browse files Browse the repository at this point in the history
- Combine pages only when they're at most half full, and not empty
- This guarantees that we'll fully empty a page, allowing us to free the memory for use by other instancers
- Track mergeable pages via a separate bitset
  • Loading branch information
Jozufozu committed Nov 2, 2024
1 parent fac6316 commit fc3e475
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ public class IndirectInstancer<I extends Instance> extends AbstractInstancer<I>
private final AtomicReference<InstancePage[]> pages;
private final AtomicBitSet changedPages = new AtomicBitSet();
private final AtomicBitSet fullPages = new AtomicBitSet();
/**
* The set of mergable pages. A page is mergeable if it is not empty and has 16 or fewer instances.
* These constraints are set so that we can guarantee that merging two pages leaves one entirely empty,
* but we also don't want to waste work merging into pages that are already empty.
*/
private final AtomicBitSet mergeablePages = new AtomicBitSet();
private final Class<I> instanceClass;

public ObjectStorage.@UnknownNullability Mapping mapping;
Expand Down Expand Up @@ -116,6 +122,13 @@ public boolean add(I instance, InstanceHandleImpl<I> handle) {
// This is safe because only one bit position changes at a time.
fullPages.set(pageNo);
}
if (isEmpty(currentValue)) {
// Value we just saw was zero, so since we added something we are now mergeable!
mergeablePages.set(pageNo);
} else if (Integer.bitCount(currentValue) == 16) {
// We just filled the 17th instance, so we are no longer mergeable.
mergeablePages.clear(pageNo);
}
return true;
}
}
Expand Down Expand Up @@ -143,8 +156,16 @@ public InstanceHandleImpl.State<I> setDeleted(int index) {
int newValue = currentValue & ~(1 << localIndex);

if (valid.compareAndSet(currentValue, newValue)) {
fullPages.clear(pageNo);
changedPages.set(pageNo);
if (isEmpty(newValue)) {
// If we decremented to zero then we're no longer mergeable.
mergeablePages.clear(pageNo);
} else if (Integer.bitCount(newValue) == 16) {
// If we decremented to 16 then we're now mergeable.
mergeablePages.set(pageNo);
}
// Set full page last so that other threads don't race to set the other bitsets.
fullPages.clear(pageNo);
return InstanceHandleImpl.Deleted.instance();
}
}
Expand All @@ -161,17 +182,12 @@ public InstanceHandleImpl.State<I> setVisible(InstanceHandleImpl<I> handle, int
return new InstanceHandleImpl.Hidden<>(recreate, instances[localIndex]);
}

public int takeFrom(InstancePage other) {
public void takeFrom(InstancePage other) {
// Fill the holes in this page with instances from the other page.

int valid = this.valid.get();
int otherValid = other.valid.get();

// If the other page is empty, or we're full, we're done.
if (isEmpty(otherValid) || isFull(valid)) {
return valid;
}

// Something is going to change, so mark stuff ahead of time.
changedPages.set(pageNo);
changedPages.set(other.pageNo);
Expand Down Expand Up @@ -208,7 +224,13 @@ public int takeFrom(InstancePage other) {
this.valid.set(valid);
other.valid.set(otherValid);

return valid;
mergeablePages.set(pageNo, isMergeable(valid));
// With all assumptions in place we should have fully drained the other page, but check just to be safe.
mergeablePages.set(other.pageNo, isMergeable(otherValid));

if (isFull(valid)) {
fullPages.set(pageNo);
}
}
}

Expand Down Expand Up @@ -303,15 +325,36 @@ public void uploadInstances(StagingBuffer stagingBuffer, int instanceVbo) {
}

public void parallelUpdate() {
// TODO: Merge pages when they're less than half full.
var pages = this.pages.get();

int page = 0;
while (mergeablePages.cardinality() > 1) {
page = mergeablePages.nextSetBit(page);
if (page < 0) {
break;
}

// Find the next mergeable page.
int next = mergeablePages.nextSetBit(page + 1);
if (next < 0) {
break;
}

// Try to merge the pages.
pages[page].takeFrom(pages[next]);
}
}

private static boolean isFull(int valid) {
return valid == 0xFFFFFFFF;
}

private static boolean isEmpty(int otherValid) {
return otherValid == 0;
private static boolean isEmpty(int valid) {
return valid == 0;
}

private static boolean isMergeable(int valid) {
return !isEmpty(valid) && Integer.bitCount(valid) <= 16;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ public AtomicBitSet(int log2SegmentSizeInBits, int numBitsToPreallocate) {
segments = new AtomicReference<>(new AtomicBitSetSegments(numSegmentsToPreallocate, numLongsPerSegment));
}

public void set(int position, boolean value) {
if (value) {
set(position);
} else {
clear(position);
}
}

public void set(int position) {
int longPosition = longIndexInSegmentForPosition(position);

Expand Down

0 comments on commit fc3e475

Please sign in to comment.