Reduced and reuse memory section, remove copies during section fetch pipeline, remove stack gathering and string forming from IdNotYetComputedException wip on cleaner

This commit is contained in:
mcrcortex
2024-12-14 16:23:46 +10:00
parent fdeed5c257
commit 6ed4c92c94
19 changed files with 130 additions and 67 deletions

View File

@@ -3,7 +3,8 @@ package me.cortex.voxy.client.core.model;
public class IdNotYetComputedException extends RuntimeException {
public final int id;
public IdNotYetComputedException(int id) {
super("Id not yet computed: " + id);
//super("Id not yet computed: " + id);
super(null, null, false, false);
this.id = id;
}
}

View File

@@ -34,7 +34,7 @@ public class NodeCleaner {
private final AutoBindingShader batchClear = Shader.makeAuto()
.define("VISIBILITY_BUFFER_BINDING", 0)
.define("LIST_BUFFER_BINDING", 1)
.add(ShaderType.COMPUTE, "voxy:lod/hierarchical/cleaner/batch_visibility_set.com")
.add(ShaderType.COMPUTE, "voxy:lod/hierarchical/cleaner/batch_visibility_set.comp")
.compile();
private final GlBuffer visibilityBuffer;
@@ -86,5 +86,6 @@ public class NodeCleaner {
this.visibilityBuffer.free();
this.outputBuffer.free();
this.scratchBuffer.free();
this.batchClear.free();
}
}

View File

@@ -11,7 +11,8 @@ import java.util.function.LongConsumer;
public abstract class StorageBackend {
public abstract void iterateStoredSectionPositions(LongConsumer consumer);
public abstract MemoryBuffer getSectionData(long key);
//Implementation may use the scratch buffer as the return value, it MUST NOT free the scratch buffer
public abstract MemoryBuffer getSectionData(long key, MemoryBuffer scratch);
public abstract void setSectionData(long key, MemoryBuffer data);

View File

@@ -4,6 +4,7 @@ import me.cortex.voxy.common.storage.StorageCompressor;
import me.cortex.voxy.common.storage.config.CompressorConfig;
import me.cortex.voxy.common.storage.config.ConfigBuildCtx;
import me.cortex.voxy.common.util.MemoryBuffer;
import me.cortex.voxy.common.util.ThreadLocalMemoryBuffer;
import me.cortex.voxy.common.world.SaveLoadSystem;
import java.lang.ref.Cleaner;
@@ -29,6 +30,8 @@ public class ZSTDCompressor implements StorageCompressor {
private static final ThreadLocal<Ref> COMPRESSION_CTX = ThreadLocal.withInitial(ZSTDCompressor::createCleanableCompressionContext);
private static final ThreadLocal<Ref> DECOMPRESSION_CTX = ThreadLocal.withInitial(ZSTDCompressor::createCleanableDecompressionContext);
private static final ThreadLocalMemoryBuffer SCRATCH = new ThreadLocalMemoryBuffer(SaveLoadSystem.BIGGEST_SERIALIZED_SECTION_SIZE + 1024);
private final int level;
public ZSTDCompressor(int level) {
@@ -44,8 +47,9 @@ public class ZSTDCompressor implements StorageCompressor {
@Override
public MemoryBuffer decompress(MemoryBuffer saveData) {
var decompressed = new MemoryBuffer(SaveLoadSystem.BIGGEST_SERIALIZED_SECTION_SIZE);
var decompressed = SCRATCH.get().createUntrackedUnfreeableReference();
long size = nZSTD_decompressDCtx(DECOMPRESSION_CTX.get().ptr, decompressed.address, decompressed.size, saveData.address, saveData.size);
//TODO:FIXME: DONT ASSUME IT DOESNT FAIL
return decompressed.subSize(size);
}

View File

@@ -45,12 +45,13 @@ public class MemoryStorageBackend extends StorageBackend {
}
@Override
public MemoryBuffer getSectionData(long key) {
public MemoryBuffer getSectionData(long key, MemoryBuffer scratch) {
var map = this.getMap(key);
synchronized (map) {
var data = map.get(key);
if (data != null) {
return data.copy();
data.cpyTo(scratch.address);
return scratch.subSize(data.size);
} else {
return null;
}

View File

@@ -92,7 +92,8 @@ public class LMDBStorageBackend extends StorageBackend {
}
//TODO: make batch get and updates
public MemoryBuffer getSectionData(long key) {
@Override
public MemoryBuffer getSectionData(long key, MemoryBuffer scratch) {
return this.synchronizedTransaction(() -> this.sectionDatabase.transaction(MDB_RDONLY, transaction->{
var buff = transaction.stack.malloc(8);
buff.putLong(0, key);
@@ -100,9 +101,8 @@ public class LMDBStorageBackend extends StorageBackend {
if (bb == null) {
return null;
}
var copy = new MemoryBuffer(bb.remaining());
UnsafeUtil.memcpy(MemoryUtil.memAddress(bb), copy.address, copy.size);
return copy;
UnsafeUtil.memcpy(MemoryUtil.memAddress(bb), scratch.address, bb.remaining());
return scratch.subSize(bb.remaining());
}));
}

View File

@@ -20,15 +20,15 @@ public class CompressionStorageAdaptor extends DelegatingStorageAdaptor {
this.compressor = compressor;
}
//TODO: figure out a nicer way w.r.t scratch buffer shit
@Override
public MemoryBuffer getSectionData(long key) {
var data = this.delegate.getSectionData(key);
public MemoryBuffer getSectionData(long key, MemoryBuffer scratch) {
var data = this.delegate.getSectionData(key, scratch);
if (data == null) {
return null;
}
var decompressed = this.compressor.decompress(data);
data.free();
return decompressed;
return this.compressor.decompress(data);
}
@Override

View File

@@ -23,8 +23,8 @@ public class DelegatingStorageAdaptor extends StorageBackend {
public void iterateStoredSectionPositions(LongConsumer consumer) {this.delegate.iterateStoredSectionPositions(consumer);}
@Override
public MemoryBuffer getSectionData(long key) {
return this.delegate.getSectionData(key);
public MemoryBuffer getSectionData(long key, MemoryBuffer scratch) {
return this.delegate.getSectionData(key, scratch);
}
@Override

View File

@@ -41,8 +41,8 @@ public class FragmentedStorageBackendAdaptor extends StorageBackend {
// multiple layers of spliced storage backends can be stacked
@Override
public MemoryBuffer getSectionData(long key) {
return this.backends[this.getSegmentId(key)].getSectionData(key);
public MemoryBuffer getSectionData(long key, MemoryBuffer scratch) {
return this.backends[this.getSegmentId(key)].getSectionData(key, scratch);
}
@Override

View File

@@ -21,12 +21,12 @@ public class ReadonlyCachingLayer extends StorageBackend {
}
@Override
public MemoryBuffer getSectionData(long key) {
var result = this.cache.getSectionData(key);
public MemoryBuffer getSectionData(long key, MemoryBuffer scratch) {
var result = this.cache.getSectionData(key, scratch);
if (result != null) {
return result;
}
result = this.onMiss.getSectionData(key);
result = this.onMiss.getSectionData(key, scratch);
if (result != null) {
this.cache.setSectionData(key, result);
}

View File

@@ -48,28 +48,28 @@ public class TranslocatingStorageAdaptor extends DelegatingStorageAdaptor {
}
@Override
public MemoryBuffer getSectionData(long key) {
public MemoryBuffer getSectionData(long key, MemoryBuffer scratch) {
for (var transform : this.transforms) {
long tpos = transform.transformIfInBox(key);
if (tpos != -1) {
if (transform.mode == Mode.BOX_ONLY || transform.mode == null) {
return super.getSectionData(tpos);
return super.getSectionData(tpos, scratch);
} else if (transform.mode == Mode.PRIORITY_BOX) {
var data = super.getSectionData(tpos);
var data = super.getSectionData(tpos, scratch);
if (data == null) {
return super.getSectionData(key);
return super.getSectionData(key, scratch);
}
} else if (transform.mode == Mode.PRIORITY_ORIGINAL) {
var data = super.getSectionData(key);
var data = super.getSectionData(key, scratch);
if (data == null) {
return super.getSectionData(tpos);
return super.getSectionData(tpos, scratch);
}
} else {
throw new IllegalStateException();
}
}
}
return super.getSectionData(key);
return super.getSectionData(key, scratch);
}
@Override

View File

@@ -37,7 +37,7 @@ public class RedisStorageBackend extends StorageBackend {
}
@Override
public MemoryBuffer getSectionData(long key) {
public MemoryBuffer getSectionData(long key, MemoryBuffer scratch) {
try (var jedis = this.pool.getResource()) {
if (this.user != null) {
jedis.auth(this.user, this.password);
@@ -48,9 +48,8 @@ public class RedisStorageBackend extends StorageBackend {
return null;
}
//Need to copy to native memory
var buffer = new MemoryBuffer(result.length);
UnsafeUtil.memcpy(result, buffer.address);
return buffer;
UnsafeUtil.memcpy(result, scratch.address);
return scratch.subSize(result.length);
}
}

View File

@@ -6,6 +6,9 @@ import me.cortex.voxy.common.storage.config.ConfigBuildCtx;
import me.cortex.voxy.common.storage.config.StorageConfig;
import me.cortex.voxy.common.util.MemoryBuffer;
import me.cortex.voxy.common.util.UnsafeUtil;
import me.cortex.voxy.common.world.SaveLoadSystem;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
import org.rocksdb.*;
import java.nio.ByteBuffer;
@@ -18,6 +21,7 @@ public class RocksDBStorageBackend extends StorageBackend {
private final RocksDB db;
private final ColumnFamilyHandle worldSections;
private final ColumnFamilyHandle idMappings;
private final ReadOptions sectionReadOps;
//NOTE: closes in order
private final List<AbstractImmutableNativeReference> closeList = new ArrayList<>();
@@ -65,10 +69,13 @@ public class RocksDBStorageBackend extends StorageBackend {
path, cfDescriptors,
handles);
this.sectionReadOps = new ReadOptions();
this.closeList.addAll(handles);
this.closeList.add(this.db);
this.closeList.add(options);
this.closeList.add(cfOpts);
this.closeList.add(this.sectionReadOps);
this.worldSections = handles.get(1);
this.idMappings = handles.get(2);
@@ -85,21 +92,30 @@ public class RocksDBStorageBackend extends StorageBackend {
}
@Override
public MemoryBuffer getSectionData(long key) {
try {
var result = this.db.get(this.worldSections, longToBytes(key));
if (result == null) {
public MemoryBuffer getSectionData(long key, MemoryBuffer scratch) {
try (var stack = MemoryStack.stackPush()){
var buffer = stack.malloc(8);
//HATE JAVA HATE JAVA HATE JAVA, Long.reverseBytes()
//THIS WILL ONLY WORK ON LITTLE ENDIAN SYSTEM AAAAAAAAA ;-;
MemoryUtil.memPutLong(MemoryUtil.memAddress(buffer), Long.reverseBytes(key));
var result = this.db.get(this.worldSections,
this.sectionReadOps,
buffer,
MemoryUtil.memByteBuffer(scratch.address, (int) (scratch.size)));
if (result == RocksDB.NOT_FOUND) {
return null;
}
//Need to copy to native memory
var buffer = new MemoryBuffer(result.length);
UnsafeUtil.memcpy(result, buffer.address);
return buffer;
return scratch.subSize(result);
} catch (RocksDBException e) {
throw new RuntimeException(e);
}
}
//TODO: FIXME, use the ByteBuffer variant
@Override
public void setSectionData(long key, MemoryBuffer data) {
try {

View File

@@ -9,6 +9,7 @@ public class MemoryBuffer extends TrackedObject {
public final long address;
public final long size;
private final boolean freeable;
private final boolean tracked;
private static final AtomicInteger COUNT = new AtomicInteger(0);
private static final AtomicLong TOTAL_SIZE = new AtomicLong(0);
@@ -20,11 +21,14 @@ public class MemoryBuffer extends TrackedObject {
private MemoryBuffer(boolean track, long address, long size, boolean freeable) {
super(track);
this.tracked = track;
this.size = size;
this.address = address;
this.freeable = freeable;
if (track) {
COUNT.incrementAndGet();
}
if (freeable) {
TOTAL_SIZE.addAndGet(size);
}
@@ -38,8 +42,9 @@ public class MemoryBuffer extends TrackedObject {
@Override
public void free() {
super.free0();
if (this.tracked) {
COUNT.decrementAndGet();
}
if (this.freeable) {
MemoryUtil.nmemFree(this.address);
TOTAL_SIZE.addAndGet(-this.size);
@@ -56,18 +61,20 @@ public class MemoryBuffer extends TrackedObject {
//Creates a new MemoryBuffer, defunking this buffer and sets the size to be a subsize of the current size
public MemoryBuffer subSize(long size) {
if (size > this.size) {
throw new IllegalArgumentException("Requested size larger than current size");
if (size > this.size || size <= 0) {
throw new IllegalArgumentException("Requested size larger than current size, or less than 0, requested: "+size+" capacity: " + this.size);
}
//Free the current object, but not the memory associated with it
this.free0();
if (this.tracked) {
COUNT.decrementAndGet();
}
if (this.freeable) {
TOTAL_SIZE.addAndGet(-this.size);
}
return new MemoryBuffer(true, this.address, size, this.freeable);
return new MemoryBuffer(this.tracked, this.address, size, this.freeable);
}
@@ -89,4 +96,8 @@ public class MemoryBuffer extends TrackedObject {
public static long getTotalSize() {
return TOTAL_SIZE.get();
}
public MemoryBuffer createUntrackedUnfreeableReference() {
return new MemoryBuffer(false, this.address, this.size, false);
}
}

View File

@@ -0,0 +1,29 @@
package me.cortex.voxy.common.util;
import me.cortex.voxy.common.storage.compressors.ZSTDCompressor;
import java.lang.ref.Cleaner;
import static org.lwjgl.util.zstd.Zstd.ZSTD_createCCtx;
import static org.lwjgl.util.zstd.Zstd.ZSTD_freeCCtx;
public class ThreadLocalMemoryBuffer {
private static final Cleaner CLEANER = Cleaner.create();
private static MemoryBuffer createMemoryBuffer(long size) {
var buffer = new MemoryBuffer(size);
var ref = MemoryBuffer.createUntrackedUnfreeableRawFrom(buffer.address, buffer.size);
CLEANER.register(ref, buffer::free);
return ref;
}
//TODO: make this much better
private final ThreadLocal<MemoryBuffer> threadLocal;
public ThreadLocalMemoryBuffer(long size) {
this.threadLocal = ThreadLocal.withInitial(()->createMemoryBuffer(size));
}
public MemoryBuffer get() {
return this.threadLocal.get();
}
}

View File

@@ -24,9 +24,11 @@ public abstract class TrackedObject {
}
this.ref.freedRef[0] = true;
if (TRACK_OBJECT_ALLOCATIONS) {
if (this.ref.cleanable != null) {
this.ref.cleanable.clean();
}
}
}
public abstract void free();

View File

@@ -1,15 +1,15 @@
package me.cortex.voxy.common.world;
import me.cortex.voxy.common.Logger;
import me.cortex.voxy.common.util.ThreadLocalMemoryBuffer;
import me.cortex.voxy.common.voxelization.VoxelizedSection;
import me.cortex.voxy.common.world.other.Mapper;
import me.cortex.voxy.common.world.service.SectionSavingService;
import me.cortex.voxy.common.world.service.VoxelIngestService;
import me.cortex.voxy.common.storage.StorageBackend;
import me.cortex.voxy.common.thread.ServiceThreadPool;
import me.cortex.voxy.commonImpl.VoxyCommon;
import java.util.Arrays;
import java.util.function.Consumer;
//Use an LMDB backend to store the world, use a local inmemory cache for lod sections
// automatically manages and invalidates sections of the world as needed
@@ -46,22 +46,20 @@ public class WorldEngine {
this.ingestService = new VoxelIngestService(this, serviceThreadPool);
}
private static final ThreadLocalMemoryBuffer MEMORY_CACHE = new ThreadLocalMemoryBuffer(SaveLoadSystem.BIGGEST_SERIALIZED_SECTION_SIZE + 1024);
private int unsafeLoadSection(WorldSection into) {
var data = this.storage.getSectionData(into.key);
var data = this.storage.getSectionData(into.key, MEMORY_CACHE.get().createUntrackedUnfreeableReference());
if (data != null) {
try {
if (!SaveLoadSystem.deserialize(into, data)) {
this.storage.deleteSectionData(into.key);
//TODO: regenerate the section from children
Arrays.fill(into.data, Mapper.AIR);
System.err.println("Section " + into.lvl + ", " + into.x + ", " + into.y + ", " + into.z + " was unable to load, removing");
Logger.error("Section " + into.lvl + ", " + into.x + ", " + into.y + ", " + into.z + " was unable to load, removing");
return -1;
} else {
return 0;
}
} finally {
data.free();
}
} else {
//TODO: if we need to fetch an lod from a server, send the request here and block until the request is finished
// the response should be put into the local db so that future data can just use that

View File

@@ -35,7 +35,7 @@ public final class WorldSection {
//TODO: should make it dynamically adjust the size allowance based on memory pressure/WorldSection allocation rate (e.g. is it doing a world import)
private static final int ARRAY_REUSE_CACHE_SIZE = 300;
private static final int ARRAY_REUSE_CACHE_SIZE = 300;//500;//32*32*32*8*ARRAY_REUSE_CACHE_SIZE == number of bytes
//TODO: maybe just swap this to a ConcurrentLinkedDeque
private static final Deque<long[]> ARRAY_REUSE_CACHE = new ArrayDeque<>(1024);

View File

@@ -13,7 +13,7 @@ layout(binding = LIST_BUFFER_BINDING, std430) restrict readonly buffer SetListBu
layout(location=0) uniform uint count;
#define SET_TO uint(-1)
void main() {
uint id = gl_InvocationID;//It might be this or gl_GlobalInvocationID.x
uint id = gl_GlobalInvocationID.x;
if (count <= id) {
return;
}