Add and use Rate Limited thread pools
This commit is contained in:
@@ -737,6 +737,11 @@ public class Config
|
||||
+ "CPU performance may suffer if Distant Horizons has a lot to load or generate. \n"
|
||||
+ "This can be an issue when first loading into a world, when flying, and/or when generating new terrain.";
|
||||
|
||||
public static final String THREAD_RUN_TIME_RATIO_NOTE = ""
|
||||
+ "If this value is less than 1.0, it will be treated as a percentage \n"
|
||||
+ "of time a each thread can run before going idle. \n"
|
||||
+ "";
|
||||
|
||||
|
||||
public static final ConfigEntry<Integer> numberOfWorldGenerationThreads = new ConfigEntry.Builder<Integer>()
|
||||
.setMinDefaultMax(1,
|
||||
@@ -753,6 +758,10 @@ public class Config
|
||||
+ "\n"
|
||||
+ THREAD_NOTE)
|
||||
.build();
|
||||
public static final ConfigEntry<Double> runTimeRatioForWorldGenerationThreads = new ConfigEntry.Builder<Double>()
|
||||
.setMinDefaultMax(0.01, 1.0, 1.0)
|
||||
.comment(THREAD_RUN_TIME_RATIO_NOTE)
|
||||
.build();
|
||||
|
||||
public static ConfigEntry<Integer> numberOfBufferBuilderThreads = new ConfigEntry.Builder<Integer>()
|
||||
.setMinDefaultMax(1,
|
||||
@@ -767,6 +776,10 @@ public class Config
|
||||
+ "\n"
|
||||
+ THREAD_NOTE)
|
||||
.build();
|
||||
public static final ConfigEntry<Double> runTimeRatioForBufferBuilderThreads = new ConfigEntry.Builder<Double>()
|
||||
.setMinDefaultMax(0.01, 1.0, 1.0)
|
||||
.comment(THREAD_RUN_TIME_RATIO_NOTE)
|
||||
.build();
|
||||
|
||||
public static final ConfigEntry<Integer> numberOfFileHandlerThreads = new ConfigEntry.Builder<Integer>()
|
||||
.setMinDefaultMax(1,
|
||||
@@ -781,6 +794,10 @@ public class Config
|
||||
+ "\n"
|
||||
+ THREAD_NOTE)
|
||||
.build();
|
||||
public static final ConfigEntry<Double> runTimeRatioForFileHandlerThreads = new ConfigEntry.Builder<Double>()
|
||||
.setMinDefaultMax(0.01, 1.0, 1.0)
|
||||
.comment(THREAD_RUN_TIME_RATIO_NOTE)
|
||||
.build();
|
||||
|
||||
public static final ConfigEntry<Integer> numberOfDataConverterThreads = new ConfigEntry.Builder<Integer>()
|
||||
.setMinDefaultMax(1,
|
||||
@@ -798,6 +815,10 @@ public class Config
|
||||
+ "\n"
|
||||
+ THREAD_NOTE)
|
||||
.build();
|
||||
public static final ConfigEntry<Double> runTimeRatioForDataConverterThreads = new ConfigEntry.Builder<Double>()
|
||||
.setMinDefaultMax(0.01, 1.0, 1.0)
|
||||
.comment(THREAD_RUN_TIME_RATIO_NOTE)
|
||||
.build();
|
||||
|
||||
public static final ConfigEntry<Integer> numberOfChunkLodConverterThreads = new ConfigEntry.Builder<Integer>()
|
||||
.setMinDefaultMax(1,
|
||||
@@ -811,6 +832,10 @@ public class Config
|
||||
+ "\n"
|
||||
+ THREAD_NOTE)
|
||||
.build();
|
||||
public static final ConfigEntry<Double> runTimeRatioForChunkLodConverterThreads = new ConfigEntry.Builder<Double>()
|
||||
.setMinDefaultMax(0.01, 1.0, 1.0)
|
||||
.comment(THREAD_RUN_TIME_RATIO_NOTE)
|
||||
.build();
|
||||
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -393,7 +393,7 @@ public class ColumnRenderBufferBuilder
|
||||
bufferBuilderThreadPool.shutdown();
|
||||
}
|
||||
|
||||
bufferBuilderThreadPool = ThreadUtil.makeThreadPool(threadPoolSize, "Buffer Builder");
|
||||
bufferBuilderThreadPool = ThreadUtil.makeRateLimitedThreadPool(threadPoolSize, "Buffer Builder", Config.Client.Advanced.MultiThreading.runTimeRatioForBufferBuilderThreads);
|
||||
maxNumberOfConcurrentCalls = threadPoolSize * MAX_NUMBER_OF_CONCURRENT_CALLS_PER_THREAD;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -230,7 +230,7 @@ public class ChunkToLodBuilder implements AutoCloseable
|
||||
}
|
||||
|
||||
threadCount = threadPoolSize;
|
||||
executorThreadPool = ThreadUtil.makeThreadPool(threadPoolSize, ChunkToLodBuilder.class);
|
||||
executorThreadPool = ThreadUtil.makeRateLimitedThreadPool(threadPoolSize, ChunkToLodBuilder.class.getSimpleName(), Config.Client.Advanced.MultiThreading.runTimeRatioForChunkLodConverterThreads);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -95,7 +95,7 @@ public class DataRenderTransformer
|
||||
transformerThreadPool.shutdown();
|
||||
}
|
||||
|
||||
transformerThreadPool = ThreadUtil.makeThreadPool(threadPoolSize, "Full/Render Data Transformer");
|
||||
transformerThreadPool = ThreadUtil.makeRateLimitedThreadPool(threadPoolSize, "Full/Render Data Transformer", Config.Client.Advanced.MultiThreading.runTimeRatioForDataConverterThreads);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -586,7 +586,7 @@ public class FullDataFileHandler implements IFullDataSourceProvider
|
||||
fileHandlerThreadPool.shutdown();
|
||||
}
|
||||
|
||||
fileHandlerThreadPool = ThreadUtil.makeThreadPool(threadPoolSize, FullDataFileHandler.class.getSimpleName()+"Thread");
|
||||
fileHandlerThreadPool = ThreadUtil.makeRateLimitedThreadPool(threadPoolSize, FullDataFileHandler.class.getSimpleName()+"Thread", Config.Client.Advanced.MultiThreading.runTimeRatioForFileHandlerThreads);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+10
-1
@@ -510,7 +510,16 @@ public class WorldGenerationQueue implements Closeable, IDebugRenderable
|
||||
setThreadPoolSize(Config.Client.Advanced.MultiThreading.numberOfWorldGenerationThreads.get());
|
||||
}
|
||||
}
|
||||
public static void setThreadPoolSize(int threadPoolSize) { worldGeneratorThreadPool = ThreadUtil.makeThreadPool(threadPoolSize, "DH-Gen-Worker-Thread", Thread.MIN_PRIORITY); }
|
||||
public static void setThreadPoolSize(int threadPoolSize)
|
||||
{
|
||||
if (worldGeneratorThreadPool != null)
|
||||
{
|
||||
// close the previous thread pool if one exists
|
||||
worldGeneratorThreadPool.shutdown();
|
||||
}
|
||||
|
||||
worldGeneratorThreadPool = ThreadUtil.makeRateLimitedThreadPool(threadPoolSize, "DH-Gen-Worker-Thread", Thread.MIN_PRIORITY, Config.Client.Advanced.MultiThreading.runTimeRatioForWorldGenerationThreads);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops any executing tasks and destroys the executor. <br>
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package com.seibel.distanthorizons.core.util;
|
||||
|
||||
import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
|
||||
import com.seibel.distanthorizons.core.config.types.ConfigEntry;
|
||||
import com.seibel.distanthorizons.core.util.objects.DhThreadFactory;
|
||||
import com.seibel.distanthorizons.core.util.objects.RateLimitedThreadPoolExecutor;
|
||||
|
||||
import java.util.concurrent.*;
|
||||
|
||||
@@ -9,9 +12,33 @@ public class ThreadUtil
|
||||
public static int MINIMUM_RELATIVE_PRIORITY = -5;
|
||||
public static int DEFAULT_RELATIVE_PRIORITY = 0;
|
||||
|
||||
// TODO currently only used for RateLimitedThreadPools could this be used for all thread pools?
|
||||
/** used to track and remove old listeners for certain pools if the thread pool is recreated. */
|
||||
private static final ConcurrentHashMap<String, ConfigChangeListener<Double>> THREAD_CHANGE_LISTENERS_BY_THREAD_NAME = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
|
||||
// create rate limited thread pool //
|
||||
|
||||
public static RateLimitedThreadPoolExecutor makeRateLimitedThreadPool(int poolSize, String name, ConfigEntry<Double> runTimeRatioConfigEntry) { return makeRateLimitedThreadPool(poolSize, name, 0, runTimeRatioConfigEntry); }
|
||||
public static RateLimitedThreadPoolExecutor makeRateLimitedThreadPool(int poolSize, String name, int relativePriority, ConfigEntry<Double> runTimeRatioConfigEntry)
|
||||
{
|
||||
// remove the old listener if one exists
|
||||
if (THREAD_CHANGE_LISTENERS_BY_THREAD_NAME.containsKey(name))
|
||||
{
|
||||
// note: this assumes only one thread pool exists with a given name
|
||||
THREAD_CHANGE_LISTENERS_BY_THREAD_NAME.get(name).close();
|
||||
THREAD_CHANGE_LISTENERS_BY_THREAD_NAME.remove(name);
|
||||
}
|
||||
|
||||
RateLimitedThreadPoolExecutor executor = new RateLimitedThreadPoolExecutor(poolSize, runTimeRatioConfigEntry.get(), new DhThreadFactory("DH-" + name, Thread.NORM_PRIORITY+relativePriority));
|
||||
|
||||
ConfigChangeListener<Double> changeListener = new ConfigChangeListener<>(runTimeRatioConfigEntry, (newRunTimeRatio) -> { executor.runTimeRatio = newRunTimeRatio; });
|
||||
THREAD_CHANGE_LISTENERS_BY_THREAD_NAME.put(name, changeListener);
|
||||
|
||||
return executor;
|
||||
}
|
||||
|
||||
|
||||
// create thread pool //
|
||||
|
||||
@@ -26,38 +53,17 @@ public class ThreadUtil
|
||||
new DhThreadFactory("DH-" + name, Thread.NORM_PRIORITY+relativePriority));
|
||||
}
|
||||
|
||||
public static ThreadPoolExecutor makeThreadPool(int poolSize, Class<?> clazz, int relativePriority)
|
||||
{
|
||||
return makeThreadPool(poolSize, clazz.getSimpleName(), relativePriority);
|
||||
}
|
||||
public static ThreadPoolExecutor makeThreadPool(int poolSize, String name)
|
||||
{
|
||||
return makeThreadPool(poolSize, name, 0);
|
||||
}
|
||||
public static ThreadPoolExecutor makeThreadPool(int poolSize, Class<?> clazz)
|
||||
{
|
||||
return makeThreadPool(poolSize, clazz.getSimpleName(), 0);
|
||||
}
|
||||
public static ThreadPoolExecutor makeThreadPool(int poolSize, Class<?> clazz, int relativePriority) { return makeThreadPool(poolSize, clazz.getSimpleName(), relativePriority); }
|
||||
public static ThreadPoolExecutor makeThreadPool(int poolSize, String name) { return makeThreadPool(poolSize, name, 0); }
|
||||
public static ThreadPoolExecutor makeThreadPool(int poolSize, Class<?> clazz) { return makeThreadPool(poolSize, clazz.getSimpleName(), 0); }
|
||||
|
||||
|
||||
// create single thread pool //
|
||||
|
||||
public static ThreadPoolExecutor makeSingleThreadPool(String name, int relativePriority)
|
||||
{
|
||||
return makeThreadPool(1, name, relativePriority);
|
||||
}
|
||||
public static ThreadPoolExecutor makeSingleThreadPool(Class<?> clazz, int relativePriority)
|
||||
{
|
||||
return makeThreadPool(1, clazz.getSimpleName(), relativePriority);
|
||||
}
|
||||
public static ThreadPoolExecutor makeSingleThreadPool(String name)
|
||||
{
|
||||
return makeThreadPool(1, name, 0);
|
||||
}
|
||||
public static ThreadPoolExecutor makeSingleThreadPool(Class<?> clazz)
|
||||
{
|
||||
return makeThreadPool(1, clazz.getSimpleName(), 0);
|
||||
}
|
||||
public static ThreadPoolExecutor makeSingleThreadPool(String name, int relativePriority) { return makeThreadPool(1, name, relativePriority); }
|
||||
public static ThreadPoolExecutor makeSingleThreadPool(Class<?> clazz, int relativePriority) { return makeThreadPool(1, clazz.getSimpleName(), relativePriority); }
|
||||
public static ThreadPoolExecutor makeSingleThreadPool(String name) { return makeThreadPool(1, name, 0); }
|
||||
public static ThreadPoolExecutor makeSingleThreadPool(Class<?> clazz) { return makeThreadPool(1, clazz.getSimpleName(), 0); }
|
||||
|
||||
|
||||
}
|
||||
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
package com.seibel.distanthorizons.core.util.objects;
|
||||
|
||||
import java.util.concurrent.*;
|
||||
|
||||
/**
|
||||
* Can be used to more finely control CPU usage and
|
||||
* reduce CPU usage if only 1 thread is already assigned.
|
||||
*/
|
||||
public class RateLimitedThreadPoolExecutor extends ThreadPoolExecutor
|
||||
{
|
||||
public volatile double runTimeRatio;
|
||||
|
||||
/** When this thread started running its last task */
|
||||
private final ThreadLocal<Long> runStartNanoTimeRef = ThreadLocal.withInitial(() -> -1L);
|
||||
/** How long it took this thread to run its last task */
|
||||
private final ThreadLocal<Long> lastRunDurationNanoTimeRef = ThreadLocal.withInitial(() -> -1L);
|
||||
|
||||
|
||||
|
||||
//==============//
|
||||
// constructors //
|
||||
//==============//
|
||||
|
||||
public RateLimitedThreadPoolExecutor(int corePoolSize, double runTimeRatio, ThreadFactory threadFactory)
|
||||
{
|
||||
super(corePoolSize, corePoolSize,
|
||||
0L, TimeUnit.MILLISECONDS,
|
||||
new LinkedBlockingQueue<>(),
|
||||
threadFactory);
|
||||
|
||||
this.runTimeRatio = runTimeRatio;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//===========//
|
||||
// overrides //
|
||||
//===========//
|
||||
|
||||
protected void beforeExecute(Thread thread, Runnable runnable)
|
||||
{
|
||||
super.beforeExecute(thread, runnable);
|
||||
|
||||
if (this.runTimeRatio < 1.0 && this.lastRunDurationNanoTimeRef.get() != -1)
|
||||
{
|
||||
try
|
||||
{
|
||||
long deltaMs = TimeUnit.NANOSECONDS.toMillis(this.lastRunDurationNanoTimeRef.get());
|
||||
Thread.sleep((long) (deltaMs/this.runTimeRatio - deltaMs));
|
||||
} catch (InterruptedException ignored) { }
|
||||
}
|
||||
|
||||
this.runStartNanoTimeRef.set(System.nanoTime());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void afterExecute(Runnable runnable, Throwable throwable)
|
||||
{
|
||||
super.afterExecute(runnable, throwable);
|
||||
this.lastRunDurationNanoTimeRef.set(System.nanoTime() - this.runStartNanoTimeRef.get());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -306,22 +306,36 @@
|
||||
"NO. of world generation threads",
|
||||
"distanthorizons.config.client.advanced.multiThreading.numberOfWorldGenerationThreads.@tooltip":
|
||||
"How many threads should be used when generating LODs \n outside the normal render distance? \n\nIf it this is less than 1, it will be treated as a percentage \nof time a single thread can run before going idle. \n\nIf you experience stuttering when generating distant LODs, \ndecrease this number. If you want to increase LOD \ngeneration speed, increase this number. \n\nThis and the number of buffer builder threads are independent, \nif they add up to more threads than your CPU has cores \nthat is ok.",
|
||||
"distanthorizons.config.client.advanced.multiThreading.runTimeRatioForWorldGenerationThreads":
|
||||
"Runtime % for world generation threads",
|
||||
|
||||
"distanthorizons.config.client.advanced.multiThreading.numberOfBufferBuilderThreads":
|
||||
"NO. of buffer builder threads",
|
||||
"distanthorizons.config.client.advanced.multiThreading.numberOfBufferBuilderThreads.@tooltip":
|
||||
"The number of threads used when building geometry data. \nCan only be between 1 and your CPU's processor count.",
|
||||
"distanthorizons.config.client.advanced.multiThreading.runTimeRatioForBufferBuilderThreads":
|
||||
"Runtime % for buffer builder threads",
|
||||
|
||||
"distanthorizons.config.client.advanced.multiThreading.numberOfFileHandlerThreads":
|
||||
"NO. of file handler threads",
|
||||
"distanthorizons.config.client.advanced.multiThreading.numberOfFileHandlerThreads.@tooltip":
|
||||
"The number of threads used when building vertex buffers \n(The things sent to your GPU to draw the LODs). \nCan only be between 1 and your CPU's processor count.",
|
||||
"distanthorizons.config.client.advanced.multiThreading.runTimeRatioForFileHandlerThreads":
|
||||
"Runtime % for file handler threads",
|
||||
|
||||
"distanthorizons.config.client.advanced.multiThreading.numberOfDataConverterThreads":
|
||||
"NO. of data converter threads",
|
||||
"distanthorizons.config.client.advanced.multiThreading.numberOfDataConverterThreads.@tooltip":
|
||||
"The number of threads used when converting ID data to render-able data. \n(This generally happens when generating new terrain or changing graphics settings). \nCan only be between 1 and your CPU's processor count.",
|
||||
"distanthorizons.config.client.advanced.multiThreading.runTimeRatioForDataConverterThreads":
|
||||
"Runtime % for data converter threads",
|
||||
|
||||
"distanthorizons.config.client.advanced.multiThreading.numberOfChunkLodConverterThreads":
|
||||
"NO. of chunk LOD converter threads",
|
||||
"distanthorizons.config.client.advanced.multiThreading.numberOfChunkLodConverterThreads.@tooltip":
|
||||
"How many threads should be used to convert Minecraft chunks into LOD data? \nThese threads run both when terrain is generated and when \nchunks are loaded, unloaded, and modified.",
|
||||
"distanthorizons.config.client.advanced.multiThreading.runTimeRatioForChunkLodConverterThreads":
|
||||
"Runtime % for chunk LOD converter threads",
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user