Skip to content

Commit

Permalink
fix measurement behavior
Browse files Browse the repository at this point in the history
  • Loading branch information
douira committed Jan 7, 2024
1 parent 106efc8 commit 0f198ba
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public class SodiumWorldRenderer {
private boolean useEntityCulling;

private RenderSectionManager renderSectionManager;
public final Measurement measurement = new Measurement();

/**
* @return The SodiumWorldRenderer based on the current dimension
Expand Down Expand Up @@ -154,7 +155,7 @@ public void setupTerrain(Camera camera,

this.useEntityCulling = SodiumClientMod.options().performance.useEntityCulling;

if (this.client.options.getClampedViewDistance() != this.renderDistance || Measurement.shouldReloadWorld()) {
if (this.client.options.getClampedViewDistance() != this.renderDistance || this.measurement.shouldReloadWorld()) {
this.reload();
}

Expand Down Expand Up @@ -198,12 +199,12 @@ public void setupTerrain(Camera camera,
}

if (this.renderSectionManager.needsUpdate()) {
Measurement.registerFrame(true);
this.measurement.registerFrame(true);
profiler.swap("chunk_render_lists");

this.renderSectionManager.update(camera, viewport, frame, spectator);
} else {
Measurement.registerFrame(false);
this.measurement.registerFrame(false);
}

profiler.swap("chunk_update");
Expand Down Expand Up @@ -259,7 +260,7 @@ private void initRenderer(CommandList commandList) {
this.renderSectionManager = null;
}

Measurement.reset();
this.measurement.reset();

this.renderDistance = this.client.options.getClampedViewDistance();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,50 @@
import org.apache.logging.log4j.Logger;

import it.unimi.dsi.fastutil.objects.ReferenceArrayList;
import me.jellysquid.mods.sodium.client.render.SodiumWorldRenderer;

/**
* Compression results on 1992 sections:
* compression candidates 55084, compression performed 1202 (ratio: 2.1%)
* uncompressed size 397665, compressed size 170944 (ratio: 42.9%)
* Removing the compresson minimum size results in a total compression ratio of
* 34% and a 92% success rate. This isn't much of an improvement, it seems the
* large candidates make up most of the compressable data. Increasing the
* minimum size to 16 lowers the success rate to 3.4% while the total
* compression ratio is 39%.
*
* test scenario: test world, 1991 events, total 538121 quads, 32 rd, 15 chunk
* builder threads
*
* at 128406c9743eab8ec90dfceeac34af6fe932af97
* (baseline):
* sort 15-23ns per quad avg, build 230-233ns per quad avg
*
* at 2aabb0a7a6a54f139db3cc2beb83881219561678
* (with compression of interval points as longs):
* sort 15-23ns per quad avg, build 150-165ns per quad avg
*
* at 4f1fb35495b3e5adab2bbc9823cbd6cbf2e5b438
* (with sorting compressed interval points as longs):
* sort 15-23ns per quad avg, build 130-140ns per quad avg
*
* at d4f220080c2bf980e8f920d4ad96e4c8be465db1
* (fixed child partition planes not being added to workspace on node reuse):
* rebuild with node reuse 120ns per quad avg,
* rebuild without node reuse 202ns per quad avg
* previously it was more like 105ns per quad avg but the child partition planes
* were missing (though it wasn't noticeable in many situations)
*
* typical heuristic values for hermitcraft 7 world:
* HEURISTIC_BOUNDING_BOX: 21
* HEURISTIC_OPPOSING_UNALIGNED: 17
* HEURISTIC_BSP_OPPOSING_UNALIGNED: 14194
* This happens because fluid render products quads that have an aligned normal
* but don't have an aligned facing because they're just slightly slanted.
*
* Distance sorting speeds on forzen ocean ice test with auto reload
* Arrays.sort with long packing: 51ns per quad avg after 20 epochs
*/
public class Measurement {
public static final boolean DEBUG_ONLY_TOPO_OR_DISTANCE_SORT = false;
public static final boolean DEBUG_SKIP_TOPO_SORT = false;
Expand All @@ -13,36 +56,91 @@ public class Measurement {
public static final boolean DEBUG_DISABLE_FRUSTUM_CULLING = false;
public static final boolean DEBUG_ONLY_RENDER_CURRENT_SECTION = false;

private static final boolean AUTO_RELOAD_WORLD = false;
private static final boolean AUTO_RELOAD_WORLD = true;
private static final boolean ACCUMULATE_MEASUREMENTS = true;

static final Logger LOGGER = LogManager.getLogger(Measurement.class);

private static int framesWithoutGraphUpdate = 0;
private static ReferenceArrayList<TimingRecorder> recorders = new ReferenceArrayList<>();
private static enum ResetState {
INITIAL,
WARMUP,
MEASUREMENT
}

private int framesWithoutGraphUpdate = 0;
private final ReferenceArrayList<TimingRecorder> recorders = new ReferenceArrayList<>();
private int currentEpoch = 0;
private ResetState resetState = ResetState.INITIAL;

public static Measurement instance() {
return SodiumWorldRenderer.instance().measurement;
}

public static void registerFrame(boolean graphHasUpdate) {
public void registerFrame(boolean graphHasUpdate) {
if (graphHasUpdate) {
framesWithoutGraphUpdate = 0;
this.framesWithoutGraphUpdate = 0;
} else {
framesWithoutGraphUpdate++;
this.framesWithoutGraphUpdate++;
}
}

public static boolean shouldReloadWorld() {
return framesWithoutGraphUpdate > 300 && AUTO_RELOAD_WORLD;
public boolean shouldReloadWorld() {
return this.framesWithoutGraphUpdate > 300 && AUTO_RELOAD_WORLD;
}

static void registerRecorder(TimingRecorder recorder) {
recorders.add(recorder);
void registerRecorder(TimingRecorder recorder) {
this.recorders.add(recorder);
}

public static void reset() {
framesWithoutGraphUpdate = 0;
public void reset() {
if (this.resetState == ResetState.INITIAL) {
LOGGER.info("Started measurement");
this.resetState = ResetState.WARMUP;
return;
}

this.framesWithoutGraphUpdate = 0;

// check if all this.recorders have been warmed up
int warmedUpCount = 0;
for (var recorder : this.recorders) {
if (recorder.checkWarmup()) {
warmedUpCount++;
}
}

if (warmedUpCount < this.recorders.size()) {
LOGGER.info(warmedUpCount + " out of " + this.recorders.size() + " recorders have been warmed up");

for (var recorder : this.recorders) {
recorder.reset();
}
} else if (this.resetState == ResetState.WARMUP) {
// message on initial warmup and set reset flag for initial reset
LOGGER.info("All recorders have been warmed up. Starting measurement.");
this.resetState = ResetState.MEASUREMENT;
this.currentEpoch = 1;

for (var recorder : this.recorders) {
recorder.reset();
}
} else {
LOGGER.info(this.currentEpoch + " epochs complete");
for (var recorder : this.recorders) {
recorder.print();

if (!ACCUMULATE_MEASUREMENTS) {
recorder.reset();
}
}

this.currentEpoch++;

for (var recorder : recorders) {
recorder.resetAfterWarmup();
printAndResetCounters();
}
}

private void printAndResetCounters() {
for (var counter : Counter.values()) {
LOGGER.info(counter + ": " + counter.getValue());
}
Expand All @@ -68,4 +166,5 @@ public static void reset() {

Counter.resetAll();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,56 +2,18 @@

import it.unimi.dsi.fastutil.objects.ReferenceArrayList;

/**
* Compression results on 1992 sections:
* compression candidates 55084, compression performed 1202 (ratio: 2.1%)
* uncompressed size 397665, compressed size 170944 (ratio: 42.9%)
* Removing the compresson minimum size results in a total compression ratio of
* 34% and a 92% success rate. This isn't much of an improvement, it seems the
* large candidates make up most of the compressable data. Increasing the
* minimum size to 16 lowers the success rate to 3.4% while the total
* compression ratio is 39%.
*
* test scenario: test world, 1991 events, total 538121 quads, 32 rd, 15 chunk
* builder threads
*
* at 128406c9743eab8ec90dfceeac34af6fe932af97
* (baseline):
* sort 15-23ns per quad avg, build 230-233ns per quad avg
*
* at 2aabb0a7a6a54f139db3cc2beb83881219561678
* (with compression of interval points as longs):
* sort 15-23ns per quad avg, build 150-165ns per quad avg
*
* at 4f1fb35495b3e5adab2bbc9823cbd6cbf2e5b438
* (with sorting compressed interval points as longs):
* sort 15-23ns per quad avg, build 130-140ns per quad avg
*
* at d4f220080c2bf980e8f920d4ad96e4c8be465db1
* (fixed child partition planes not being added to workspace on node reuse):
* rebuild with node reuse 120ns per quad avg,
* rebuild without node reuse 202ns per quad avg
* previously it was more like 105ns per quad avg but the child partition planes
* were missing (though it wasn't noticeable in many situations)
*
* typical heuristic values for hermitcraft 7 world:
* HEURISTIC_BOUNDING_BOX: 21
* HEURISTIC_OPPOSING_UNALIGNED: 17
* HEURISTIC_BSP_OPPOSING_UNALIGNED: 14194
* This happens because fluid render products quads that have an aligned normal
* but don't have an aligned facing because they're just slightly slanted.
*/
public class TimingRecorder {
static record TimedEvent(int size, long ns) {
}

private static final int WARMUP_COUNT = 500;
private static final int WARMUP_COUNT = 5000;

private ReferenceArrayList<TimedEvent> events = new ReferenceArrayList<>(1000);
private boolean warmedUp = false;

private final String name;
private int remainingWarmup;
private boolean receivedEvents;
private boolean printEvents;
private boolean printData;

Expand All @@ -60,7 +22,7 @@ public TimingRecorder(String name, int warmupCount, boolean printEvents) {
this.remainingWarmup = warmupCount;
this.printEvents = printEvents;

Measurement.registerRecorder(this);
Measurement.instance().registerRecorder(this);
}

public TimingRecorder(String name, int warmupCount) {
Expand All @@ -80,6 +42,7 @@ public void recordDelta(int size, long startNanos, long endNanos) {
}

synchronized public void recordDelta(int size, long delta) {
receivedEvents = true;
if (!this.warmedUp) {
this.remainingWarmup--;
if (this.remainingWarmup == 0) {
Expand All @@ -96,7 +59,15 @@ synchronized public void recordDelta(int size, long delta) {
}
}

private boolean isEmpty() {
return this.events.isEmpty();
}

public void print() {
if (this.isEmpty()) {
return;
}

var builder = new StringBuilder();
builder.append("size,ns\n");

Expand Down Expand Up @@ -133,16 +104,15 @@ public void print() {
}
}

void resetAfterWarmup() {
if (this.remainingWarmup <= 0) {
if (!this.events.isEmpty()) {
this.print();
}

boolean checkWarmup() {
if (!this.warmedUp && this.remainingWarmup <= 0) {
this.warmedUp = true;
Measurement.LOGGER.info("Started recorder " + this.name);
}
return this.warmedUp || !this.receivedEvents;
}

void reset() {
this.events.clear();
Measurement.LOGGER.info("Started recorder " + this.name);
}
}

0 comments on commit 0f198ba

Please sign in to comment.