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 class IdNotYetComputedException extends RuntimeException {
public final int id; public final int id;
public IdNotYetComputedException(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; this.id = id;
} }
} }

View File

@@ -34,7 +34,7 @@ public class NodeCleaner {
private final AutoBindingShader batchClear = Shader.makeAuto() private final AutoBindingShader batchClear = Shader.makeAuto()
.define("VISIBILITY_BUFFER_BINDING", 0) .define("VISIBILITY_BUFFER_BINDING", 0)
.define("LIST_BUFFER_BINDING", 1) .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(); .compile();
private final GlBuffer visibilityBuffer; private final GlBuffer visibilityBuffer;
@@ -86,5 +86,6 @@ public class NodeCleaner {
this.visibilityBuffer.free(); this.visibilityBuffer.free();
this.outputBuffer.free(); this.outputBuffer.free();
this.scratchBuffer.free(); this.scratchBuffer.free();
this.batchClear.free();
} }
} }

View File

@@ -11,7 +11,8 @@ import java.util.function.LongConsumer;
public abstract class StorageBackend { public abstract class StorageBackend {
public abstract void iterateStoredSectionPositions(LongConsumer consumer); 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); 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.CompressorConfig;
import me.cortex.voxy.common.storage.config.ConfigBuildCtx; import me.cortex.voxy.common.storage.config.ConfigBuildCtx;
import me.cortex.voxy.common.util.MemoryBuffer; import me.cortex.voxy.common.util.MemoryBuffer;
import me.cortex.voxy.common.util.ThreadLocalMemoryBuffer;
import me.cortex.voxy.common.world.SaveLoadSystem; import me.cortex.voxy.common.world.SaveLoadSystem;
import java.lang.ref.Cleaner; 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> COMPRESSION_CTX = ThreadLocal.withInitial(ZSTDCompressor::createCleanableCompressionContext);
private static final ThreadLocal<Ref> DECOMPRESSION_CTX = ThreadLocal.withInitial(ZSTDCompressor::createCleanableDecompressionContext); 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; private final int level;
public ZSTDCompressor(int level) { public ZSTDCompressor(int level) {
@@ -37,15 +40,16 @@ public class ZSTDCompressor implements StorageCompressor {
@Override @Override
public MemoryBuffer compress(MemoryBuffer saveData) { public MemoryBuffer compress(MemoryBuffer saveData) {
MemoryBuffer compressedData = new MemoryBuffer((int)ZSTD_COMPRESSBOUND(saveData.size)); MemoryBuffer compressedData = new MemoryBuffer((int)ZSTD_COMPRESSBOUND(saveData.size));
long compressedSize = nZSTD_compressCCtx(COMPRESSION_CTX.get().ptr, compressedData.address, compressedData.size, saveData.address, saveData.size, this.level); long compressedSize = nZSTD_compressCCtx(COMPRESSION_CTX.get().ptr, compressedData.address, compressedData.size, saveData.address, saveData.size, this.level);
return compressedData.subSize(compressedSize); return compressedData.subSize(compressedSize);
} }
@Override @Override
public MemoryBuffer decompress(MemoryBuffer saveData) { 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); 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); return decompressed.subSize(size);
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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.storage.config.StorageConfig;
import me.cortex.voxy.common.util.MemoryBuffer; import me.cortex.voxy.common.util.MemoryBuffer;
import me.cortex.voxy.common.util.UnsafeUtil; 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 org.rocksdb.*;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@@ -18,6 +21,7 @@ public class RocksDBStorageBackend extends StorageBackend {
private final RocksDB db; private final RocksDB db;
private final ColumnFamilyHandle worldSections; private final ColumnFamilyHandle worldSections;
private final ColumnFamilyHandle idMappings; private final ColumnFamilyHandle idMappings;
private final ReadOptions sectionReadOps;
//NOTE: closes in order //NOTE: closes in order
private final List<AbstractImmutableNativeReference> closeList = new ArrayList<>(); private final List<AbstractImmutableNativeReference> closeList = new ArrayList<>();
@@ -65,10 +69,13 @@ public class RocksDBStorageBackend extends StorageBackend {
path, cfDescriptors, path, cfDescriptors,
handles); handles);
this.sectionReadOps = new ReadOptions();
this.closeList.addAll(handles); this.closeList.addAll(handles);
this.closeList.add(this.db); this.closeList.add(this.db);
this.closeList.add(options); this.closeList.add(options);
this.closeList.add(cfOpts); this.closeList.add(cfOpts);
this.closeList.add(this.sectionReadOps);
this.worldSections = handles.get(1); this.worldSections = handles.get(1);
this.idMappings = handles.get(2); this.idMappings = handles.get(2);
@@ -85,21 +92,30 @@ public class RocksDBStorageBackend extends StorageBackend {
} }
@Override @Override
public MemoryBuffer getSectionData(long key) { public MemoryBuffer getSectionData(long key, MemoryBuffer scratch) {
try { try (var stack = MemoryStack.stackPush()){
var result = this.db.get(this.worldSections, longToBytes(key)); var buffer = stack.malloc(8);
if (result == null) { //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; return null;
} }
//Need to copy to native memory
var buffer = new MemoryBuffer(result.length); return scratch.subSize(result);
UnsafeUtil.memcpy(result, buffer.address);
return buffer;
} catch (RocksDBException e) { } catch (RocksDBException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
//TODO: FIXME, use the ByteBuffer variant
@Override @Override
public void setSectionData(long key, MemoryBuffer data) { public void setSectionData(long key, MemoryBuffer data) {
try { try {

View File

@@ -9,6 +9,7 @@ public class MemoryBuffer extends TrackedObject {
public final long address; public final long address;
public final long size; public final long size;
private final boolean freeable; private final boolean freeable;
private final boolean tracked;
private static final AtomicInteger COUNT = new AtomicInteger(0); private static final AtomicInteger COUNT = new AtomicInteger(0);
private static final AtomicLong TOTAL_SIZE = new AtomicLong(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) { private MemoryBuffer(boolean track, long address, long size, boolean freeable) {
super(track); super(track);
this.tracked = track;
this.size = size; this.size = size;
this.address = address; this.address = address;
this.freeable = freeable; this.freeable = freeable;
COUNT.incrementAndGet(); if (track) {
COUNT.incrementAndGet();
}
if (freeable) { if (freeable) {
TOTAL_SIZE.addAndGet(size); TOTAL_SIZE.addAndGet(size);
} }
@@ -38,8 +42,9 @@ public class MemoryBuffer extends TrackedObject {
@Override @Override
public void free() { public void free() {
super.free0(); super.free0();
if (this.tracked) {
COUNT.decrementAndGet(); COUNT.decrementAndGet();
}
if (this.freeable) { if (this.freeable) {
MemoryUtil.nmemFree(this.address); MemoryUtil.nmemFree(this.address);
TOTAL_SIZE.addAndGet(-this.size); 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 //Creates a new MemoryBuffer, defunking this buffer and sets the size to be a subsize of the current size
public MemoryBuffer subSize(long size) { public MemoryBuffer subSize(long size) {
if (size > this.size) { if (size > this.size || size <= 0) {
throw new IllegalArgumentException("Requested size larger than current size"); 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 //Free the current object, but not the memory associated with it
this.free0(); this.free0();
COUNT.decrementAndGet(); if (this.tracked) {
COUNT.decrementAndGet();
}
if (this.freeable) { if (this.freeable) {
TOTAL_SIZE.addAndGet(-this.size); 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() { public static long getTotalSize() {
return TOTAL_SIZE.get(); 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,7 +24,9 @@ public abstract class TrackedObject {
} }
this.ref.freedRef[0] = true; this.ref.freedRef[0] = true;
if (TRACK_OBJECT_ALLOCATIONS) { if (TRACK_OBJECT_ALLOCATIONS) {
this.ref.cleanable.clean(); if (this.ref.cleanable != null) {
this.ref.cleanable.clean();
}
} }
} }

View File

@@ -1,15 +1,15 @@
package me.cortex.voxy.common.world; 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.voxelization.VoxelizedSection;
import me.cortex.voxy.common.world.other.Mapper; import me.cortex.voxy.common.world.other.Mapper;
import me.cortex.voxy.common.world.service.SectionSavingService; import me.cortex.voxy.common.world.service.SectionSavingService;
import me.cortex.voxy.common.world.service.VoxelIngestService; import me.cortex.voxy.common.world.service.VoxelIngestService;
import me.cortex.voxy.common.storage.StorageBackend; import me.cortex.voxy.common.storage.StorageBackend;
import me.cortex.voxy.common.thread.ServiceThreadPool; import me.cortex.voxy.common.thread.ServiceThreadPool;
import me.cortex.voxy.commonImpl.VoxyCommon;
import java.util.Arrays; 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 //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 // automatically manages and invalidates sections of the world as needed
@@ -46,21 +46,19 @@ public class WorldEngine {
this.ingestService = new VoxelIngestService(this, serviceThreadPool); 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) { 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) { if (data != null) {
try { if (!SaveLoadSystem.deserialize(into, data)) {
if (!SaveLoadSystem.deserialize(into, data)) { this.storage.deleteSectionData(into.key);
this.storage.deleteSectionData(into.key); //TODO: regenerate the section from children
//TODO: regenerate the section from children Arrays.fill(into.data, Mapper.AIR);
Arrays.fill(into.data, Mapper.AIR); Logger.error("Section " + into.lvl + ", " + into.x + ", " + into.y + ", " + into.z + " was unable to load, removing");
System.err.println("Section " + into.lvl + ", " + into.x + ", " + into.y + ", " + into.z + " was unable to load, removing"); return -1;
return -1; } else {
} else { return 0;
return 0;
}
} finally {
data.free();
} }
} else { } else {
//TODO: if we need to fetch an lod from a server, send the request here and block until the request is finished //TODO: if we need to fetch an lod from a server, send the request here and block until the request is finished

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) //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 //TODO: maybe just swap this to a ConcurrentLinkedDeque
private static final Deque<long[]> ARRAY_REUSE_CACHE = new ArrayDeque<>(1024); 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; layout(location=0) uniform uint count;
#define SET_TO uint(-1) #define SET_TO uint(-1)
void main() { void main() {
uint id = gl_InvocationID;//It might be this or gl_GlobalInvocationID.x uint id = gl_GlobalInvocationID.x;
if (count <= id) { if (count <= id) {
return; return;
} }