A Minecraft mod that performs extremely detailed performance analysis of mods during startup, world loading, and world generation.
WhySoSlow is released for multiple Minecraft versions on separate branches:
| Branch | Minecraft | Loader | Java |
|---|---|---|---|
1.20.1 |
1.20.1 | Forge | 17 |
1.21.1 |
1.21.1 | NeoForge | 21 |
26.1 |
26.1 | NeoForge | 25 |
Artifacts are published to maven.breakinblocks.com with version format <minecraft-version>-<mod-version> (e.g., whysoslow-1.21.1-1.0.4.jar) and attached to GitHub releases tagged v<minecraft-version>-<mod-version>.
This mod is a debugging tool only. Do not ship it in production modpacks.
WhySoSlow uses Mixin to inject into loader internals and Minecraft's worldgen pipeline. Where loader internals cannot be transformed (module-layer classes), it uses reflection to wrap event bus listeners directly. This means:
- It is itself a source of performance loss. Every mod event, every placed feature, every structure, every carver, and every noise fill operation is wrapped with timing instrumentation. This overhead is unavoidable and will make your game slower while the mod is installed.
- It mixes into critical code paths. Mixin class transformations are applied to Minecraft worldgen classes including
MinecraftServer,ChunkGenerator,NoiseBasedChunkGenerator,PlacedFeature,StructureStart, andConfiguredWorldCarver. While these injections are carefully written, they carry inherent risk. - It modifies event bus listener lists at runtime. To capture per-mod timing during startup and world load, WhySoSlow uses reflection to wrap every other mod's registered event handlers.
- Install it, collect your data, then remove it. Do not leave this mod installed longer than necessary.
On every game launch, WhySoSlow times every mod's handling of every lifecycle event. When loading completes, a report is written to:
logs/whysoslow/startup.log
The report includes for every mod, sorted slowest to fastest:
- Wall-clock time, CPU time, and memory delta per loading phase (Construction, Registry, Common Setup, Client Setup, Load Complete)
- Registry entry counts (blocks, items, entities, etc.)
- Overall phase breakdown and total startup time
When you load into a world, WhySoSlow measures dimension creation time, memory usage, and per-mod contributions during the server start lifecycle. A report is written to:
logs/whysoslow/worldload.log
Use the /whysoslow command to profile runtime world generation:
| Command | Description |
|---|---|
/whysoslow start |
Begin profiling. Builds registry lookup maps and starts recording all worldgen operations. |
/whysoslow stop |
Stop profiling and write the report. |
/whysoslow status |
Show current profiling state and stats. |
Explore the world while profiling is active to generate chunks, then stop to get your report at:
logs/whysoslow/worldgen.log
The worldgen report breaks down:
- Overall category split - Noise generation, surface building, feature placement, structure generation, and carvers with percentages
- Every placed feature - Total time, average time, max time, and call count, attributed to the owning mod
- Features grouped by mod - Which mods' worldgen features cost the most
- Every structure - Generation time per structure type
- Every carver - Cave/ravine carving time per carver type
| Mixin | Target | Purpose |
|---|---|---|
MixinMinecraftServer |
MinecraftServer.createLevels / prepareLevels |
Dimension creation and chunk load timing |
MixinServerLevel |
ServerLevel.<init> |
Per-dimension construction timing |
MixinChunkGenerator |
ChunkGenerator.applyBiomeDecoration |
Per-chunk decoration timing |
MixinNoiseBasedChunkGenerator |
fillFromNoise, buildSurface |
Terrain and surface timing |
MixinPlacedFeature |
PlacedFeature.placeWithBiomeCheck |
Per-feature timing |
MixinStructureStart |
StructureStart.placeInChunk |
Per-structure timing |
MixinConfiguredWorldCarver |
ConfiguredWorldCarver.carve |
Per-carver timing |
Each branch targets a specific Java version (see the table above). Point JAVA_HOME at the matching JDK before running Gradle:
export JAVA_HOME="/path/to/matching-jdk"
./gradlew buildOutput JAR will be in build/libs/.