I am puzzled why setting GCSettings.LatencyMode
to GCLatencyMode.LowLatency
negatively impacts the time of execution?
Please consider the following code. Note that I have sufficient threads in the thread pool so I ensure there is no latency introduced here. Also, I have plenty of memory available on this machine. The difference between running in Interactive
and LowLatency
causes a 3 fold increase in execution time for LowLatency
.
class Program
{
static void Main(string[] args)
{
//capture current latency mode
var currentLatencyMode = GCSettings.LatencyMode;
//set low latency mode to minimize garbage collection
GCSettings.LatencyMode = GCLatencyMode.LowLatency;
var watch = new Stopwatch();
var numberTasksToSpinOff = 4;
var numberItems = 20000;
var random = new Random((int)DateTime.Now.Ticks);
var dataPoints = Enumerable.Range(1, numberItems).Select(x => random.NextDouble()).ToList();
var workers = new List<Worker>();
//structure workers
for (int i = 1; i <= numberTasksToSpinOff; i++)
{
workers.Add(new Worker(i, dataPoints));
}
//start timer
watch.Restart();
//parallel work
if (workers.Any())
{
var processorCount = Environment.ProcessorCount;
var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = processorCount };
Parallel.ForEach(workers, parallelOptions, DoSomeWork);
}
//stop timer
watch.Stop();
//reset latency mode
GCSettings.LatencyMode = currentLatencyMode;
Console.WriteLine($"Time it took to complete in Milliseconds: {watch.ElapsedMilliseconds}");
Console.WriteLine("Press key to quit");
Console.ReadLine();
}
private static void DoSomeWork(Worker worker)
{
Console.WriteLine($"WorkerId: {worker.WorkerId} -> New Tasks spun off with in Thread Id: {Thread.CurrentThread.ManagedThreadId}");
var indexPos = 0;
foreach (var dp in worker.DataPoints)
{
var subset = worker.DataPoints.Skip(indexPos).Take(worker.DataPoints.Count - indexPos).ToList();
indexPos++;
}
}
}
public class Worker
{
public int WorkerId { get; set; }
public List<double> DataPoints { get; set; }
public Worker(int workerId, List<double> dataPoints)
{
WorkerId = workerId;
DataPoints = dataPoints;
}
}
There is no free lunch here, the garbage collector has to do a job and tries to take your concerns into consideration. However there is no one size fits all (especially when trying to push its limits).
To reclaim objects, the garbage collector must stop all the executing threads in an application. In some situations, such as when an application retrieves data or displays content, a full garbage collection can occur at a critical time and impede performance. You can adjust the intrusiveness of the garbage collector by setting the
GCSettings.LatencyMode
property to one of theSystem.Runtime.GCLatencyMode
values
Further more
LowLatency suppresses generation 2 collections and performs only generation 0 and 1 collections. It can be used only for short periods of time. Over longer periods, if the system is under memory pressure, the garbage collector will trigger a collection, which can briefly pause the application and disrupt a time-critical operation. This setting is available only for workstation garbage collection.
During low latency periods, generation 2 collections are suppressed unless the following occurs:
Guidelines for Using Low Latency
When you use LowLatency mode, consider the following guidelines:
Keep the period of time in low latency as short as possible.
Avoid allocating high amounts of memory during low latency periods. Low memory notifications can occur because garbage collection reclaims fewer objects.
While in the low latency mode, minimize the number of allocations you make, in particular allocations onto the Large Object Heap and pinned objects.
Be aware of threads that could be allocating. Because the LatencyMode property setting is process-wide, you could generate an OutOfMemoryException on any thread that may be allocating.
...
As per the guidelines (and taking into consideration your previous question, How to properly parallelize worker tasks?) you are obviously trying to use it against its intended ideal operating conditions.
I think the most important points for you are 1 and 3, obviously the garbage collector is either being forced to cleanup by a gc.collect
command or it feels it needs to cleanup the massive amount of memory you are using allocating, i.e 11 gigs.
The key here, is without knowing the exact internals and working of the garbage collector and knowing exactly what you are doing and the reason why, there may not ever be an ideal answer to your question to say anything other than "in your your situation it does impact execution time"