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:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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) {
|
||||
@@ -37,15 +40,16 @@ public class ZSTDCompressor implements StorageCompressor {
|
||||
|
||||
@Override
|
||||
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);
|
||||
return compressedData.subSize(compressedSize);
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
COUNT.incrementAndGet();
|
||||
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();
|
||||
|
||||
COUNT.decrementAndGet();
|
||||
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();
|
||||
COUNT.decrementAndGet();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,9 @@ public abstract class TrackedObject {
|
||||
}
|
||||
this.ref.freedRef[0] = true;
|
||||
if (TRACK_OBJECT_ALLOCATIONS) {
|
||||
this.ref.cleanable.clean();
|
||||
if (this.ref.cleanable != null) {
|
||||
this.ref.cleanable.clean();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,21 +46,19 @@ 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");
|
||||
return -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
} finally {
|
||||
data.free();
|
||||
if (!SaveLoadSystem.deserialize(into, data)) {
|
||||
this.storage.deleteSectionData(into.key);
|
||||
//TODO: regenerate the section from children
|
||||
Arrays.fill(into.data, Mapper.AIR);
|
||||
Logger.error("Section " + into.lvl + ", " + into.x + ", " + into.y + ", " + into.z + " was unable to load, removing");
|
||||
return -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
//TODO: if we need to fetch an lod from a server, send the request here and block until the request is finished
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user