From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Spottedleaf Date: Thu, 2 Mar 2023 23:19:04 -0800 Subject: [PATCH] Cache whether region files do not exist The repeated I/O of creating the directory for the regionfile or for checking if the file exists can be heavy in when pushing chunk generation extremely hard - as each chunk gen request may effectively go through to the I/O thread. diff --git a/src/main/java/io/papermc/paper/chunk/system/io/RegionFileIOThread.java b/src/main/java/io/papermc/paper/chunk/system/io/RegionFileIOThread.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/io/papermc/paper/chunk/system/io/RegionFileIOThread.java +++ b/src/main/java/io/papermc/paper/chunk/system/io/RegionFileIOThread.java @@ -0,0 +0,0 @@ public final class RegionFileIOThread extends PrioritisedQueueExecutorThread { return file.hasChunk(chunkPos) ? Boolean.TRUE : Boolean.FALSE; }); } else { + // first check if the region file for sure does not exist + if (taskController.doesRegionFileNotExist(chunkX, chunkZ)) { + return Boolean.FALSE; + } // else: it either exists or is not known, fall back to checking the loaded region file + return taskController.computeForRegionFileIfLoaded(chunkX, chunkZ, (final RegionFile file) -> { if (file == null) { // null if not loaded + // not sure at this point, let the I/O thread figure it out return Boolean.TRUE; } @@ -0,0 +0,0 @@ public final class RegionFileIOThread extends PrioritisedQueueExecutorThread { return !this.tasks.isEmpty(); } + public boolean doesRegionFileNotExist(final int chunkX, final int chunkZ) { + return this.getCache().doesRegionFileNotExistNoIO(new ChunkPos(chunkX, chunkZ)); + } + public T computeForRegionFile(final int chunkX, final int chunkZ, final boolean existingOnly, final Function function) { final RegionFileStorage cache = this.getCache(); final RegionFile regionFile; diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java @@ -0,0 +0,0 @@ public class RegionFileStorage implements AutoCloseable { private final Path folder; private final boolean sync; + // Paper start - cache regionfile does not exist state + static final int MAX_NON_EXISTING_CACHE = 1024 * 64; + private final it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet nonExistingRegionFiles = new it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet(); + private synchronized boolean doesRegionFilePossiblyExist(long position) { + if (this.nonExistingRegionFiles.contains(position)) { + this.nonExistingRegionFiles.addAndMoveToFirst(position); + return false; + } + return true; + } + + private synchronized void createRegionFile(long position) { + this.nonExistingRegionFiles.remove(position); + } + + private synchronized void markNonExisting(long position) { + if (this.nonExistingRegionFiles.addAndMoveToFirst(position)) { + while (this.nonExistingRegionFiles.size() >= MAX_NON_EXISTING_CACHE) { + this.nonExistingRegionFiles.removeLastLong(); + } + } + } + + public synchronized boolean doesRegionFileNotExistNoIO(ChunkPos pos) { + long key = ChunkPos.asLong(pos.getRegionX(), pos.getRegionZ()); + return !this.doesRegionFilePossiblyExist(key); + } + // Paper end - cache regionfile does not exist state + protected RegionFileStorage(Path directory, boolean dsync) { // Paper - protected constructor this.folder = directory; this.sync = dsync; @@ -0,0 +0,0 @@ public class RegionFileStorage implements AutoCloseable { } public synchronized RegionFile getRegionFile(ChunkPos chunkcoordintpair, boolean existingOnly, boolean lock) throws IOException { // Paper end - long i = ChunkPos.asLong(chunkcoordintpair.getRegionX(), chunkcoordintpair.getRegionZ()); + long i = ChunkPos.asLong(chunkcoordintpair.getRegionX(), chunkcoordintpair.getRegionZ()); final long regionPos = i; // Paper - OBFHELPER RegionFile regionfile = (RegionFile) this.regionCache.getAndMoveToFirst(i); if (regionfile != null) { @@ -0,0 +0,0 @@ public class RegionFileStorage implements AutoCloseable { // Paper end return regionfile; } else { + // Paper start - cache regionfile does not exist state + if (existingOnly && !this.doesRegionFilePossiblyExist(regionPos)) { + return null; + } + // Paper end - cache regionfile does not exist state if (this.regionCache.size() >= 256) { ((RegionFile) this.regionCache.removeLast()).close(); } - FileUtil.createDirectoriesSafe(this.folder); + // Paper - only create directory if not existing only - moved down Path path = this.folder; int j = chunkcoordintpair.getRegionX(); Path path1 = path.resolve("r." + j + "." + chunkcoordintpair.getRegionZ() + ".mca"); - if (existingOnly && !java.nio.file.Files.exists(path1)) return null; // CraftBukkit + if (existingOnly && !java.nio.file.Files.exists(path1)) { // Paper start - cache regionfile does not exist state + this.markNonExisting(regionPos); + return null; // CraftBukkit + } else { + this.createRegionFile(regionPos); + } + // Paper end - cache regionfile does not exist state + FileUtil.createDirectoriesSafe(this.folder); // Paper - only create directory if not existing only - moved from above RegionFile regionfile1 = new RegionFile(path1, this.folder, this.sync); this.regionCache.putAndMoveToFirst(i, regionfile1);