From c866fbfbfd56e89cacfd20f41d25bce39888fc4b Mon Sep 17 00:00:00 2001 From: James Seibel Date: Mon, 4 Mar 2024 07:10:20 -0600 Subject: [PATCH] Add update propagator thread pool --- .../distanthorizons/core/config/Config.java | 27 +++++++++++++++- .../ThreadPresetConfigEventHandler.java | 25 +++++++++++++++ .../file/AbstractNewDataSourceHandler.java | 31 ++++++++++++------- .../fullDatafile/NewFullDataFileHandler.java | 9 +++--- .../renderfile/RenderSourceFileHandler.java | 13 +++++--- .../core/util/threading/ThreadPoolUtil.java | 10 +++++- .../assets/distanthorizons/lang/en_us.json | 7 +++++ 7 files changed, 101 insertions(+), 21 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java b/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java index f81eb8ce9..a3a39aefb 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java @@ -907,7 +907,7 @@ public class Config ThreadPresetConfigEventHandler.getFileHandlerDefaultThreadCount(), Runtime.getRuntime().availableProcessors()) .comment("" - + "How many threads should be used when reading in LOD data from disk? \n" + + "How many threads should be used when reading/writing LOD data to/from disk? \n" + "\n" + "Increasing this number will cause LODs to load in faster, \n" + "but may cause lag when loading a new world or when \n" @@ -920,6 +920,31 @@ public class Config .comment(THREAD_RUN_TIME_RATIO_NOTE) .build(); + public static final ConfigEntry numberOfUpdatePropagatorThreads = new ConfigEntry.Builder() + .setMinDefaultMax(1, + ThreadPresetConfigEventHandler.getUpdatePropagatorDefaultThreadCount(), + Runtime.getRuntime().availableProcessors()) + .comment("" + + "How many threads should be used when applying LOD updates? \n" + + "An LOD update is the operation of down-sampling a high detail LOD \n" + + "into a lower detail one.\n" + + "\n" + + "This config can have a much higher number of threads \n" + + "assigned and much lower run time ratio vs other thread pools \n" + + "because the amount of time any particular thread may run is relatively low.\n" + + "\n" + + "This is because LOD updating only only partially thread safe, \n" + + "so between 40% and 60% of the time a given thread may end up \n" + + "waiting on another thread to finish updating the same LOD it also wants\n" + + "to work on.\n" + + "\n" + + THREAD_NOTE) + .build(); + public static final ConfigEntry runTimeRatioForUpdatePropagatorThreads = new ConfigEntry.Builder() + .setMinDefaultMax(0.01, ThreadPresetConfigEventHandler.getUpdatePropagatorDefaultRunTimeRatio(), 1.0) + .comment(THREAD_RUN_TIME_RATIO_NOTE) + .build(); + public static final ConfigEntry numberOfLodBuilderThreads = new ConfigEntry.Builder() .setMinDefaultMax(1, ThreadPresetConfigEventHandler.getLodBuilderDefaultThreadCount(), diff --git a/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/presets/ThreadPresetConfigEventHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/presets/ThreadPresetConfigEventHandler.java index 67b7e2010..fee7bb252 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/presets/ThreadPresetConfigEventHandler.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/presets/ThreadPresetConfigEventHandler.java @@ -85,6 +85,28 @@ public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHan }}); + public static int getUpdatePropagatorDefaultThreadCount() { return getThreadCountByPercent(0.5); } + private final ConfigEntryWithPresetOptions UpdatePropagatorThreadCount = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.numberOfUpdatePropagatorThreads, + new HashMap() + {{ + this.put(EThreadPreset.MINIMAL_IMPACT, 1); + this.put(EThreadPreset.LOW_IMPACT, getUpdatePropagatorDefaultThreadCount()); + this.put(EThreadPreset.BALANCED, getThreadCountByPercent(0.75)); + this.put(EThreadPreset.AGGRESSIVE, getThreadCountByPercent(0.75)); + this.put(EThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, getThreadCountByPercent(1.0)); + }}); + public static double getUpdatePropagatorDefaultRunTimeRatio() { return 0.25; } + private final ConfigEntryWithPresetOptions UpdatePropagatorRunTime = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.runTimeRatioForUpdatePropagatorThreads, + new HashMap() + {{ + this.put(EThreadPreset.MINIMAL_IMPACT, 0.25); + this.put(EThreadPreset.LOW_IMPACT, getUpdatePropagatorDefaultRunTimeRatio()); + this.put(EThreadPreset.BALANCED, 0.5); + this.put(EThreadPreset.AGGRESSIVE, 1.0); + this.put(EThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, 1.0); + }}); + + public static int getLodBuilderDefaultThreadCount() { return getThreadCountByPercent(0.1); } private final ConfigEntryWithPresetOptions lodBuilderThreadCount = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.numberOfLodBuilderThreads, new HashMap() @@ -122,6 +144,9 @@ public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHan this.configList.add(this.fileHandlerThreadCount); this.configList.add(this.fileHandlerRunTime); + this.configList.add(this.UpdatePropagatorThreadCount); + this.configList.add(this.UpdatePropagatorRunTime); + this.configList.add(this.lodBuilderThreadCount); this.configList.add(this.lodBuilderRunTime); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/AbstractNewDataSourceHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/file/AbstractNewDataSourceHandler.java index c6ec0a08a..99eb57815 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/AbstractNewDataSourceHandler.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/AbstractNewDataSourceHandler.java @@ -168,7 +168,7 @@ public abstract class AbstractNewDataSourceHandler @Override public CompletableFuture updateDataSourceAsync(NewFullDataSource inputDataSource) { - ThreadPoolExecutor executor = ThreadPoolUtil.getFileHandlerExecutor(); + ThreadPoolExecutor executor = ThreadPoolUtil.getUpdatePropagatorExecutor(); if (executor == null || executor.isTerminated()) { return CompletableFuture.completedFuture(null); @@ -214,18 +214,27 @@ public abstract class AbstractNewDataSourceHandler if (dataModified) { - // save the updated data to the database - TDTO dto = this.createDtoFromDataSource(dataSource); - this.repo.save(dto); - - - for (IDataSourceUpdateFunc listener : this.dateSourceUpdateListeners) + ThreadPoolExecutor executor = ThreadPoolUtil.getFileHandlerExecutor(); + if (executor == null || executor.isTerminated()) { - if (listener != null) - { - listener.OnDataSourceUpdated(dataSource); - } + return; } + + executor.execute(() -> + { + // save the updated data to the database + TDTO dto = this.createDtoFromDataSource(dataSource); + this.repo.save(dto); + + + for (IDataSourceUpdateFunc listener : this.dateSourceUpdateListeners) + { + if (listener != null) + { + listener.OnDataSourceUpdated(dataSource); + } + } + }); } } catch (Exception e) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/NewFullDataFileHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/NewFullDataFileHandler.java index 59a3ccc3b..3233b55ae 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/NewFullDataFileHandler.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/NewFullDataFileHandler.java @@ -58,7 +58,7 @@ public class NewFullDataFileHandler private static final int MAX_PARENT_UPDATE_TASK_COUNT = NUMBER_OF_PARENT_UPDATE_TASKS_PER_THREAD * Config.Client.Advanced.MultiThreading.numberOfFileHandlerThreads.get(); /** indicates how long the update queue thread should wait between queuing ticks */ - private static final int UPDATE_QUEUE_THREAD_DELAY_IN_MS = 500; + private static final int UPDATE_QUEUE_THREAD_DELAY_IN_MS = 1_000; /** the list of queued positions that need to update their parents */ Set parentApplicationPositionSet = ConcurrentHashMap.newKeySet(); @@ -210,7 +210,7 @@ public class NewFullDataFileHandler if (updatePosList.size() != 0) { // stop if the file handler has been shut down - ThreadPoolExecutor executor = ThreadPoolUtil.getFileHandlerExecutor(); + ThreadPoolExecutor executor = ThreadPoolUtil.getUpdatePropagatorExecutor(); if (executor == null || executor.isTerminated()) { this.updateQueueThreadRunningRef.set(false); @@ -222,6 +222,9 @@ public class NewFullDataFileHandler int queueCount = 0; for (DhSectionPos pos : updatePosList) { + // James thought batching together updates + // based on the parent they were going to update would reduce update locks, + // but after testing it didn't, so we're just queing each section individually if (this.parentApplicationPositionSet.add(pos)) { queueCount++; @@ -230,8 +233,6 @@ public class NewFullDataFileHandler NewFullDataSource inputData = this.get(pos); // update the parent position with this new data this.updateDataSourceAtPos(pos.getParentPos(), inputData, true); - - // TODO add comparable interface to make this low priority }); } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/RenderSourceFileHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/RenderSourceFileHandler.java index 70ce500bf..1724a6a5b 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/RenderSourceFileHandler.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/RenderSourceFileHandler.java @@ -126,13 +126,18 @@ public class RenderSourceFileHandler extends AbstractLegacyDataSourceHandler lines = new ArrayList<>(); lines.add("File Handler [" + this.level.getLevelWrapper().getDimensionType().getDimensionName() + "]"); - lines.add(" Thread pool tasks: " + queueSize + " (completed: " + completedTaskSize + ")"); + lines.add(" File thread pool tasks: " + fileQueueSize + " (completed: " + fileCompletedTaskSize + ")"); + lines.add(" Update thread pool tasks: " + updateQueueSize + " (completed: " + updateCompletedTaskSize + ")"); lines.add(" Unsaved render sources: " + this.unsavedDataSourceBySectionPos.size()); return lines.toArray(new String[0]); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/threading/ThreadPoolUtil.java b/core/src/main/java/com/seibel/distanthorizons/core/util/threading/ThreadPoolUtil.java index 6924ab7b2..1d78ea34e 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/util/threading/ThreadPoolUtil.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/threading/ThreadPoolUtil.java @@ -46,11 +46,17 @@ public class ThreadPoolUtil @Nullable public static ThreadPoolExecutor getFileHandlerExecutor() { return fileHandlerThreadPool.executor; } + public static final DhThreadFactory UPDATE_PROPAGATOR_THREAD_FACTORY = new DhThreadFactory("LOD Update Propagator", Thread.MIN_PRIORITY); + private static RateLimitedThreadPoolExecutor updatePropagatorThreadPool; + @Nullable + public static ThreadPoolExecutor getUpdatePropagatorExecutor() { return updatePropagatorThreadPool; } + public static final DhThreadFactory WORLD_GEN_THREAD_FACTORY = new DhThreadFactory("World Gen", Thread.MIN_PRIORITY); private static ConfigThreadPool worldGenThreadPool; @Nullable public static ThreadPoolExecutor getWorldGenExecutor() { return worldGenThreadPool.executor; } + public static final String BUFFER_UPLOADER_THREAD_NAME = "Buffer Uploader"; private static ThreadPoolExecutor bufferUploaderThreadPool; @Nullable public static ThreadPoolExecutor getBufferUploaderExecutor() { return bufferUploaderThreadPool; } @@ -99,8 +105,9 @@ public class ThreadPoolUtil // standalone threads // fileHandlerThreadPool = new ConfigThreadPool(FILE_HANDLER_THREAD_FACTORY, Config.Client.Advanced.MultiThreading.numberOfFileHandlerThreads, Config.Client.Advanced.MultiThreading.runTimeRatioForFileHandlerThreads, null); + updatePropagatorThreadPool = new RateLimitedThreadPoolExecutor(8, 1.0, UPDATE_PROPAGATOR_THREAD_FACTORY); worldGenThreadPool = new ConfigThreadPool(WORLD_GEN_THREAD_FACTORY, Config.Client.Advanced.MultiThreading.numberOfWorldGenerationThreads, Config.Client.Advanced.MultiThreading.runTimeRatioForWorldGenerationThreads, null); - bufferUploaderThreadPool = ThreadUtil.makeSingleThreadPool("Buffer Uploader"); + bufferUploaderThreadPool = ThreadUtil.makeSingleThreadPool(BUFFER_UPLOADER_THREAD_NAME); @@ -138,6 +145,7 @@ public class ThreadPoolUtil { // standalone threads fileHandlerThreadPool.shutdownExecutorService(); + updatePropagatorThreadPool.shutdown(); worldGenThreadPool.shutdownExecutorService(); bufferUploaderThreadPool.shutdown(); diff --git a/core/src/main/resources/assets/distanthorizons/lang/en_us.json b/core/src/main/resources/assets/distanthorizons/lang/en_us.json index b6a02475e..f581ea4aa 100644 --- a/core/src/main/resources/assets/distanthorizons/lang/en_us.json +++ b/core/src/main/resources/assets/distanthorizons/lang/en_us.json @@ -395,6 +395,13 @@ "distanthorizons.config.client.advanced.multiThreading.runTimeRatioForFileHandlerThreads": "Runtime % for file handler threads", + "distanthorizons.config.client.advanced.multiThreading.numberOfUpdatePropagatorThreads": + "NO. of update propagator threads", + "distanthorizons.config.client.advanced.multiThreading.numberOfUpdatePropagatorThreads.@tooltip": + "How many threads should be used when applying LOD updates? \nAn LOD update is the operation of down-sampling a high detail LOD \ninto a lower detail one. \n\nThis config can have a much higher number of threads \nassigned and much lower run time ratio vs other thread pools \nbecause the amount of time any particular thread may run is relatively low.", + "distanthorizons.config.client.advanced.multiThreading.runTimeRatioForUpdatePropagatorThreads": + "Runtime % for update propagator threads", + "distanthorizons.config.client.advanced.multiThreading.numberOfLodBuilderThreads": "NO. of LOD builder threads", "distanthorizons.config.client.advanced.multiThreading.numberOfLodBuilderThreads.@tooltip":