From f715396d98e9bc2f27c9ff3dd5297df35068a381 Mon Sep 17 00:00:00 2001 From: t-jekor Date: Mon, 5 Jun 2017 15:27:37 -0700 Subject: [PATCH 01/13] Ported Hill Climbing from CoreCLR to CoreRT. --- .../src/System.Private.CoreLib.csproj | 2 + .../ClrThreadPool.HillClimbing.Unix.cs | 380 ++++++++++++++++++ .../System/Threading/ClrThreadPool.Unix.cs | 45 +++ 3 files changed, 427 insertions(+) create mode 100644 src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.Unix.cs create mode 100644 src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.Unix.cs diff --git a/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj b/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj index 34f9aacb316..ef367ccfddf 100644 --- a/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj +++ b/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj @@ -170,6 +170,8 @@ + + diff --git a/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.Unix.cs b/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.Unix.cs new file mode 100644 index 00000000000..e66eae533a9 --- /dev/null +++ b/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.Unix.cs @@ -0,0 +1,380 @@ +namespace System.Threading +{ + internal static partial class ClrThreadPool + { + // Config values pulled from CoreCLR + private static readonly HillClimbing s_threadPoolHillClimber = new HillClimbing(4, 20, 100, 8, 15, 300, 4, 20, 10, 200, 1, 200, 15); + + private class HillClimbing + { + public enum State + { + Warmup, + Initializing, + RandomMove, + ClimbingMove, + ChangePoint, + Stabilizing, + Starvation, + ThreadTimedOut, + } + + private State _currentState; + + private readonly int _wavePeriod; + private readonly int _samplesToMeasure; + private readonly double _targetThroughputRatio; + private readonly double _targetSignalToNoiseRatio; + private readonly double _maxChangePerSecond; + private readonly double _maxChangePerSample; + private readonly int _maxThreadWaveMagnitude; + private readonly int _sampleIntervalLow; + private readonly double _threadMagnitudeMultiplier; + private readonly int _sampleIntervalHigh; + private readonly double _throughputErrorSmoothingFactor; + private readonly double _gainExponent; + private readonly double _maxSampleError; + + private double _currentControlSetting; + private int _totalSamples; + private int _lastThreadCount; + private double _averageThroughputNoise; + private double _secondsElapsedSinceLastChange; + private double _completionsSinceLastChange; + private int _accumulatedCompletionCount; + private double _accumulatedSampleDuration; + private double[] _samples; + private double[] _threadCounts; + private int _currentSampleInterval; + + private Random _randomIntervalGenerator = new Random(); + + public HillClimbing(int wavePeriod, int maxWaveMagnitude, double waveMagnitudeMultiplier, int waveHistorySize, double targetThroughputRatio, + double targetSignalToNoiseRatio, double maxChangePerSecond, double maxChangePerSample, int sampleIntervalLow, int sampleIntervalHigh, + double errorSmoothingFactor, double gainExponent, double maxSampleError) + { + _wavePeriod = wavePeriod; + _maxThreadWaveMagnitude = maxWaveMagnitude; + _threadMagnitudeMultiplier = waveMagnitudeMultiplier / 100.0; + _samplesToMeasure = wavePeriod * waveHistorySize; + _targetThroughputRatio = targetThroughputRatio / 100.0; + _targetSignalToNoiseRatio = targetSignalToNoiseRatio / 100.0; + _maxChangePerSecond = maxChangePerSecond; + _maxChangePerSample = maxChangePerSample; + _sampleIntervalLow = sampleIntervalLow; + _sampleIntervalHigh = sampleIntervalHigh; + _throughputErrorSmoothingFactor = errorSmoothingFactor / 100.0; + _gainExponent = gainExponent / 100.0; + _maxSampleError = maxSampleError / 100.0; + + _currentSampleInterval = _randomIntervalGenerator.Next(_sampleIntervalLow, _sampleIntervalHigh); + } + + public (int newThreadCount, int newSampleInterval) Update(int currentThreadCount, double sampleDuration, int numCompletions) + { + + // + // If someone changed the thread count without telling us, update our records accordingly. + // + if (currentThreadCount != _lastThreadCount) + ForceChange(currentThreadCount, State.Initializing); + + // + // Update the cumulative stats for this thread count + // + _secondsElapsedSinceLastChange += sampleDuration; + _completionsSinceLastChange += numCompletions; + + // + // Add in any data we've already collected about this sample + // + sampleDuration += _accumulatedSampleDuration; + numCompletions += _accumulatedCompletionCount; + + // + // We need to make sure we're collecting reasonably accurate data. Since we're just counting the end + // of each work item, we are goinng to be missing some data about what really happened during the + // sample interval. The count produced by each thread includes an initial work item that may have + // started well before the start of the interval, and each thread may have been running some new + // work item for some time before the end of the interval, which did not yet get counted. So + // our count is going to be off by +/- threadCount workitems. + // + // The exception is that the thread that reported to us last time definitely wasn't running any work + // at that time, and the thread that's reporting now definitely isn't running a work item now. So + // we really only need to consider threadCount-1 threads. + // + // Thus the percent error in our count is +/- (threadCount-1)/numCompletions. + // + // We cannot rely on the frequency-domain analysis we'll be doing later to filter out this error, because + // of the way it accumulates over time. If this sample is off by, say, 33% in the negative direction, + // then the next one likely will be too. The one after that will include the sum of the completions + // we missed in the previous samples, and so will be 33% positive. So every three samples we'll have + // two "low" samples and one "high" sample. This will appear as periodic variation right in the frequency + // range we're targeting, which will not be filtered by the frequency-domain translation. + // + if (_totalSamples > 0 && ((currentThreadCount - 1.0) / numCompletions) >= _maxSampleError) + { + // not accurate enough yet. Let's accumulate the data so far, and tell the ThreadPool + // to collect a little more. + _accumulatedSampleDuration = sampleDuration; + _accumulatedCompletionCount = numCompletions; + return (currentThreadCount, 10); + } + + // + // We've got enouugh data for our sample; reset our accumulators for next time. + // + _accumulatedSampleDuration = 0; + _accumulatedCompletionCount = 0; + + // + // Add the current thread count and throughput sample to our history + // + double throughput = numCompletions / sampleDuration; + //Worker Thread Adjustment Sample event + + int sampleIndex = _totalSamples % _samplesToMeasure; + _samples[sampleIndex] = throughput; + _threadCounts[sampleIndex] = currentThreadCount; + _totalSamples++; + + // + // Set up defaults for our metrics + // + (double real, double imaginary) threadWaveComponent = (0, 0); + (double real, double imaginary) throughputWaveComponent = (0, 0); + double throughputErrorEstimate = 0; + (double real, double imaginary) ratio = (0, 0); + double confidence = 0; + + State state = State.Warmup; + + // + // How many samples will we use? It must be at least the three wave periods we're looking for, and it must also be a whole + // multiple of the primary wave's period; otherwise the frequency we're looking for will fall between two frequency bands + // in the Fourier analysis, and we won't be able to measure it accurately. + // + int sampleCount = Math.Min(_totalSamples - 1, _samplesToMeasure) / _wavePeriod * _wavePeriod; + + if (sampleCount > _wavePeriod) + { + // + // Average the throughput and thread count samples, so we can scale the wave magnitudes later. + // + double sampleSum = 0; + double threadSum = 0; + for (int i = 0; i < sampleCount; i++) + { + sampleSum += _samples[(_totalSamples - sampleCount + i) % _samplesToMeasure]; + threadSum += _threadCounts[(_totalSamples - sampleCount + i) % _samplesToMeasure]; + } + double averageThroughput = sampleSum / sampleCount; + double averageThreadCount = threadSum / sampleCount; + + if (averageThroughput > 0 && averageThreadCount > 0) + { + // + // Calculate the periods of the adjacent frequency bands we'll be using to measure noise levels. + // We want the two adjacent Fourier frequency bands. + // + double adjacentPeriod1 = sampleCount / (((double)sampleCount / _wavePeriod) + 1); + double adjacentPeriod2 = sampleCount / (((double)sampleCount / _wavePeriod) - 1); + + // + // Get the the three different frequency components of the throughput (scaled by average + // throughput). Our "error" estimate (the amount of noise that might be present in the + // frequency band we're really interested in) is the average of the adjacent bands. + // + throughputWaveComponent = GetWaveComponent(_samples, sampleCount, _wavePeriod); + throughputWaveComponent = (throughputWaveComponent.real / averageThroughput, throughputWaveComponent.imaginary / averageThroughput); + (double real, double imaginary) intermediateErrorEstimate = GetWaveComponent(_samples, sampleCount, adjacentPeriod1); + throughputErrorEstimate = Abs((intermediateErrorEstimate.real / averageThroughput, intermediateErrorEstimate.imaginary / averageThroughput)); + if (adjacentPeriod2 <= sampleCount) + { + intermediateErrorEstimate = GetWaveComponent(_samples, sampleCount, adjacentPeriod2); + throughputErrorEstimate = Math.Max(throughputErrorEstimate, Abs((intermediateErrorEstimate.real / averageThroughput, intermediateErrorEstimate.imaginary / averageThroughput))); + } + + // + // Do the same for the thread counts, so we have something to compare to. We don't measure thread count + // noise, because there is none; these are exact measurements. + // + (double real, double imaginary) intermediateThreadWaveComponent = GetWaveComponent(_threadCounts, sampleCount, _wavePeriod); + threadWaveComponent = (intermediateThreadWaveComponent.real / averageThreadCount, intermediateThreadWaveComponent.imaginary / averageThreadCount); + + // + // Update our moving average of the throughput noise. We'll use this later as feedback to + // determine the new size of the thread wave. + // + if (_averageThroughputNoise == 0) + _averageThroughputNoise = throughputErrorEstimate; + else + _averageThroughputNoise = (_throughputErrorSmoothingFactor * throughputErrorEstimate) + ((1.0 - _throughputErrorSmoothingFactor) * _averageThroughputNoise); + + if (Abs(threadWaveComponent) > 0) + { + // + // Adjust the throughput wave so it's centered around the target wave, and then calculate the adjusted throughput/thread ratio. + // + ratio.real = (throughputWaveComponent.real - (_targetThroughputRatio * threadWaveComponent.real)) / threadWaveComponent.real; + ratio.imaginary = (throughputWaveComponent.imaginary - (_targetThroughputRatio * threadWaveComponent.imaginary)) / threadWaveComponent.imaginary; + state = State.ClimbingMove; + } + else + { + ratio = (0, 0); + state = State.Stabilizing; + } + + // + // Calculate how confident we are in the ratio. More noise == less confident. This has + // the effect of slowing down movements that might be affected by random noise. + // + double noiseForConfidence = Math.Max(_averageThroughputNoise, throughputErrorEstimate); + if (noiseForConfidence > 0) + confidence = (Abs(threadWaveComponent) / noiseForConfidence) / _targetSignalToNoiseRatio; + else + confidence = 1.0; //there is no noise! + + } + } + + // + // We use just the real part of the complex ratio we just calculated. If the throughput signal + // is exactly in phase with the thread signal, this will be the same as taking the magnitude of + // the complex move and moving that far up. If they're 180 degrees out of phase, we'll move + // backward (because this indicates that our changes are having the opposite of the intended effect). + // If they're 90 degrees out of phase, we won't move at all, because we can't tell wether we're + // having a negative or positive effect on throughput. + // + double move = Math.Min(1.0, Math.Max(-1.0, ratio.real)); + + // + // Apply our confidence multiplier. + // + move *= Math.Min(1.0, Math.Max(0.0, confidence)); + + // + // Now apply non-linear gain, such that values around zero are attenuated, while higher values + // are enhanced. This allows us to move quickly if we're far away from the target, but more slowly + // if we're getting close, giving us rapid ramp-up without wild oscillations around the target. + // + double gain = _maxChangePerSecond * sampleDuration; + move = Math.Pow(Math.Abs(move), _gainExponent) * (move >= 0.0 ? 1 : -1) * gain; + move = Math.Min(move, _maxChangePerSample); + + // + // If the result was positive, and CPU is > 95%, refuse the move. + // + if (move > 0.0 && s_cpuUtilization > CpuUtilizationHigh) + move = 0.0; + + // + // Apply the move to our control setting + // + _currentControlSetting += move; + + // + // Calculate the new thread wave magnitude, which is based on the moving average we've been keeping of + // the throughput error. This average starts at zero, so we'll start with a nice safe little wave at first. + // + int newThreadWaveMagnitude = (int)(0.5 + (_currentControlSetting * _averageThroughputNoise * _targetSignalToNoiseRatio * _threadMagnitudeMultiplier * 2.0)); + newThreadWaveMagnitude = Math.Min(newThreadWaveMagnitude, _maxThreadWaveMagnitude); + newThreadWaveMagnitude = Math.Max(newThreadWaveMagnitude, 1); + + // + // Make sure our control setting is within the ThreadPool's limits + // + int maxThreads = s_maxThreads; + int minThreads = s_minThreads; + + _currentControlSetting = Math.Min(maxThreads - newThreadWaveMagnitude, _currentControlSetting); + _currentControlSetting = Math.Max(minThreads, _currentControlSetting); + + // + // Calculate the new thread count (control setting + square wave) + // + int newThreadCount = (int)(_currentControlSetting + newThreadWaveMagnitude * ((_totalSamples / (_wavePeriod / 2)) % 2)); + + // + // Make sure the new thread count doesn't exceed the ThreadPool's limits + // + newThreadCount = Math.Min(maxThreads, newThreadCount); + newThreadCount = Math.Max(minThreads, newThreadCount); + + // + // Record these numbers for posterity + // + + // Worker Thread Adjustment stats event + + + // + // If all of this caused an actual change in thread count, log that as well. + // + if (newThreadCount != currentThreadCount) + ChangeThreadCount(newThreadCount, state); + + // + // Return the new thread count and sample interval. This is randomized to prevent correlations with other periodic + // changes in throughput. Among other things, this prevents us from getting confused by Hill Climbing instances + // running in other processes. + // + // If we're at minThreads, and we seem to be hurting performance by going higher, we can't go any lower to fix this. So + // we'll simply stay at minThreads much longer, and only occasionally try a higher value. + // + int newSampleInterval; + if (ratio.real < 0.0 && newThreadCount == minThreads) + newSampleInterval = (int)(0.5 + _currentSampleInterval * (10.0 * Math.Max(-ratio.real, 1.0))); + else + newSampleInterval = _currentSampleInterval; + + return (newThreadCount, newSampleInterval); + } + + private void ChangeThreadCount(int newThreadCount, State state) + { + _lastThreadCount = newThreadCount; + _currentSampleInterval = _randomIntervalGenerator.Next(_sampleIntervalLow, _sampleIntervalHigh); + double throughput = _secondsElapsedSinceLastChange > 0 ? _completionsSinceLastChange / _secondsElapsedSinceLastChange : 0; + LogTransition(newThreadCount, throughput, state); + _secondsElapsedSinceLastChange = 0; + _completionsSinceLastChange = 0; + } + + private void LogTransition(int newThreadCount, double throughput, State state) + { + // TODO: Log transitions + } + + public void ForceChange(int newThreadCount, State state) + { + if(_lastThreadCount != newThreadCount) + { + _currentControlSetting += newThreadCount - _lastThreadCount; + ChangeThreadCount(newThreadCount, state); + } + } + + private static (double real, double imaginary) GetWaveComponent(double[] samples, int numSamples, double period) + { + double w = 2 * Math.PI / period; + double cos = Math.Cos(w); + double coeff = 2 * cos; + double q0 = 0, q1 = 0, q2 = 0; + for(int i = 0; i < numSamples && i < samples.Length; ++i) + { + q0 = coeff * q1 - q2 + samples[i]; + q2 = q1; + q1 = q0; + } + return ((q1 - q2 * cos) / samples.Length, (q2 * Math.Sin(w)) / samples.Length); + } + + private static double Abs((double real, double imaginary) complex) + { + return Math.Sqrt(complex.real * complex.real + complex.imaginary * complex.imaginary); + } + } + } +} diff --git a/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.Unix.cs b/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.Unix.cs new file mode 100644 index 00000000000..f87ef565657 --- /dev/null +++ b/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.Unix.cs @@ -0,0 +1,45 @@ +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace System.Threading +{ + internal static partial class ClrThreadPool + { + private const int CpuUtilizationHigh = 95; + private const int CpuUtilizationLow = 80; + private static int s_cpuUtilization = 85; // TODO: Add calculation for CPU utilization + + private static int s_minThreads; + private static int s_maxThreads; + + public static bool SetMinThreads(int threads) + { + if (threads < 0 || threads > s_maxThreads) + { + return false; + } + else + { + s_minThreads = threads; + return true; + } + } + + public static int GetMinThreads() => s_minThreads; + + public static bool SetMaxThreads(int threads) + { + if (threads < ThreadPoolGlobals.processorCount || threads < s_minThreads) + { + return false; + } + else + { + s_maxThreads = threads; + return true; + } + } + + public static int GetMaxThreads() => s_maxThreads; + } +} From 8e813e55ebb825814f6398317e7667425ed1c54a Mon Sep 17 00:00:00 2001 From: t-jekor Date: Mon, 5 Jun 2017 16:10:20 -0700 Subject: [PATCH 02/13] Fixed compile error and added license header. --- .../System/Threading/ClrThreadPool.HillClimbing.Unix.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.Unix.cs b/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.Unix.cs index e66eae533a9..1390a39c2f6 100644 --- a/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.Unix.cs +++ b/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.Unix.cs @@ -1,4 +1,8 @@ -namespace System.Threading +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Threading { internal static partial class ClrThreadPool { @@ -67,6 +71,9 @@ public HillClimbing(int wavePeriod, int maxWaveMagnitude, double waveMagnitudeMu _gainExponent = gainExponent / 100.0; _maxSampleError = maxSampleError / 100.0; + _samples = new double[_samplesToMeasure]; + _threadCounts = new double[_samplesToMeasure]; + _currentSampleInterval = _randomIntervalGenerator.Next(_sampleIntervalLow, _sampleIntervalHigh); } From dfca925f065e4e9d437bbcf5822976451f468f58 Mon Sep 17 00:00:00 2001 From: t-jekor Date: Mon, 5 Jun 2017 16:18:49 -0700 Subject: [PATCH 03/13] New unix-specific files now compile on Unix only. Removed unused member of Hill Climbing. --- src/System.Private.CoreLib/src/System.Private.CoreLib.csproj | 4 ++-- .../src/System/Threading/ClrThreadPool.HillClimbing.Unix.cs | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj b/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj index ef367ccfddf..2c45d15221c 100644 --- a/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj +++ b/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj @@ -170,8 +170,6 @@ - - @@ -649,6 +647,8 @@ + + diff --git a/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.Unix.cs b/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.Unix.cs index 1390a39c2f6..44faf47ad57 100644 --- a/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.Unix.cs +++ b/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.Unix.cs @@ -22,9 +22,7 @@ public enum State Starvation, ThreadTimedOut, } - - private State _currentState; - + private readonly int _wavePeriod; private readonly int _samplesToMeasure; private readonly double _targetThroughputRatio; From 4506c026376455a9d0940d55c3c19711798df28b Mon Sep 17 00:00:00 2001 From: t-jekor Date: Tue, 6 Jun 2017 09:53:11 -0700 Subject: [PATCH 04/13] Make ClrThreadPool and hill climbing compile on both Windows and Unix as per PR comments. --- .../src/System.Private.CoreLib.csproj | 8 ++++---- ...HillClimbing.Unix.cs => ClrThreadPool.HillClimbing.cs} | 0 .../Threading/{ClrThreadPool.Unix.cs => ClrThreadPool.cs} | 0 3 files changed, 4 insertions(+), 4 deletions(-) rename src/System.Private.CoreLib/src/System/Threading/{ClrThreadPool.HillClimbing.Unix.cs => ClrThreadPool.HillClimbing.cs} (100%) rename src/System.Private.CoreLib/src/System/Threading/{ClrThreadPool.Unix.cs => ClrThreadPool.cs} (100%) diff --git a/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj b/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj index 2c45d15221c..147d0e5d252 100644 --- a/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj +++ b/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj @@ -1,4 +1,4 @@ - + @@ -361,6 +361,8 @@ + + @@ -647,8 +649,6 @@ - - @@ -853,4 +853,4 @@ - + \ No newline at end of file diff --git a/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.Unix.cs b/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.cs similarity index 100% rename from src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.Unix.cs rename to src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.cs diff --git a/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.Unix.cs b/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.cs similarity index 100% rename from src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.Unix.cs rename to src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.cs From 3b9c90cc01422f791c4f2ecded0409d67c04f298 Mon Sep 17 00:00:00 2001 From: t-jekor Date: Tue, 6 Jun 2017 10:29:33 -0700 Subject: [PATCH 05/13] Move CLR threadpool classes to compile on Unix only for now. --- src/System.Private.CoreLib/src/System.Private.CoreLib.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj b/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj index 147d0e5d252..2d2c73f8541 100644 --- a/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj +++ b/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj @@ -361,8 +361,6 @@ - - @@ -655,6 +653,8 @@ + + From ca78d250db853fd3b3b150db8392ebcffad3754c Mon Sep 17 00:00:00 2001 From: t-jekor Date: Tue, 6 Jun 2017 10:31:36 -0700 Subject: [PATCH 06/13] Alphabetically sort project entries. --- src/System.Private.CoreLib/src/System.Private.CoreLib.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj b/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj index 2d2c73f8541..42aa8c884c2 100644 --- a/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj +++ b/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj @@ -647,14 +647,14 @@ + + - - From 2f8e2fbd8ee8d380613634fd9f314a8ce17067b4 Mon Sep 17 00:00:00 2001 From: t-jekor Date: Tue, 6 Jun 2017 11:05:01 -0700 Subject: [PATCH 07/13] Add more TODO comments to ensure that SetMinThreads and SetMaxThreads are implemented correctly in a future PR. Moved /100 ratio calculations into config values for Hill climbing. --- .../System/Threading/ClrThreadPool.HillClimbing.cs | 14 +++++++------- .../src/System/Threading/ClrThreadPool.cs | 6 ++++-- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.cs b/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.cs index 44faf47ad57..3d8d13d8b89 100644 --- a/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.cs +++ b/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.cs @@ -7,7 +7,7 @@ namespace System.Threading internal static partial class ClrThreadPool { // Config values pulled from CoreCLR - private static readonly HillClimbing s_threadPoolHillClimber = new HillClimbing(4, 20, 100, 8, 15, 300, 4, 20, 10, 200, 1, 200, 15); + private static readonly HillClimbing s_threadPoolHillClimber = new HillClimbing(4, 20, 100 / 100.0, 8, 15 / 100.0, 300 / 100.0, 4, 20, 10, 200, 1 / 100.0, 200 / 100.0, 15 / 100.0); private class HillClimbing { @@ -57,17 +57,17 @@ public HillClimbing(int wavePeriod, int maxWaveMagnitude, double waveMagnitudeMu { _wavePeriod = wavePeriod; _maxThreadWaveMagnitude = maxWaveMagnitude; - _threadMagnitudeMultiplier = waveMagnitudeMultiplier / 100.0; + _threadMagnitudeMultiplier = waveMagnitudeMultiplier; _samplesToMeasure = wavePeriod * waveHistorySize; - _targetThroughputRatio = targetThroughputRatio / 100.0; - _targetSignalToNoiseRatio = targetSignalToNoiseRatio / 100.0; + _targetThroughputRatio = targetThroughputRatio; + _targetSignalToNoiseRatio = targetSignalToNoiseRatio; _maxChangePerSecond = maxChangePerSecond; _maxChangePerSample = maxChangePerSample; _sampleIntervalLow = sampleIntervalLow; _sampleIntervalHigh = sampleIntervalHigh; - _throughputErrorSmoothingFactor = errorSmoothingFactor / 100.0; - _gainExponent = gainExponent / 100.0; - _maxSampleError = maxSampleError / 100.0; + _throughputErrorSmoothingFactor = errorSmoothingFactor; + _gainExponent = gainExponent; + _maxSampleError = maxSampleError; _samples = new double[_samplesToMeasure]; _threadCounts = new double[_samplesToMeasure]; diff --git a/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.cs b/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.cs index f87ef565657..3ad0abc9f2f 100644 --- a/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.cs +++ b/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.cs @@ -9,9 +9,11 @@ internal static partial class ClrThreadPool private const int CpuUtilizationLow = 80; private static int s_cpuUtilization = 85; // TODO: Add calculation for CPU utilization - private static int s_minThreads; - private static int s_maxThreads; + private static int s_minThreads; // TODO: Initialize + private static int s_maxThreads; // TODO: Initialize + // TODO: SetMinThreads and SetMaxThreads need to be synchronized with a lock + // TODO: Compare with CoreCLR implementation and ensure this has the same guarantees. public static bool SetMinThreads(int threads) { if (threads < 0 || threads > s_maxThreads) From 9e19d3318da17232a35cfc59148dca76bd4908a2 Mon Sep 17 00:00:00 2001 From: t-jekor Date: Tue, 6 Jun 2017 11:39:01 -0700 Subject: [PATCH 08/13] Moved HillCimbing instance to be a static member on HillClimbing instead of ClrThreadPool --- .../src/System/Threading/ClrThreadPool.HillClimbing.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.cs b/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.cs index 3d8d13d8b89..ae8c67f9231 100644 --- a/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.cs +++ b/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.cs @@ -7,10 +7,11 @@ namespace System.Threading internal static partial class ClrThreadPool { // Config values pulled from CoreCLR - private static readonly HillClimbing s_threadPoolHillClimber = new HillClimbing(4, 20, 100 / 100.0, 8, 15 / 100.0, 300 / 100.0, 4, 20, 10, 200, 1 / 100.0, 200 / 100.0, 15 / 100.0); - + private class HillClimbing { + public static HillClimbing ThreadPoolHillClimber { get; } = new HillClimbing(4, 20, 100 / 100.0, 8, 15 / 100.0, 300 / 100.0, 4, 20, 10, 200, 1 / 100.0, 200 / 100.0, 15 / 100.0); + public enum State { Warmup, From 0aad3b8dd20e727884107132fa1ea00b9a0e99f7 Mon Sep 17 00:00:00 2001 From: t-jekor Date: Tue, 6 Jun 2017 11:51:21 -0700 Subject: [PATCH 09/13] Made changes consistent with PR feedback --- .../Threading/ClrThreadPool.HillClimbing.cs | 52 ++++++++++++------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.cs b/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.cs index ae8c67f9231..6b8d04993da 100644 --- a/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.cs +++ b/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.cs @@ -2,17 +2,20 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Diagnostics; + namespace System.Threading { internal static partial class ClrThreadPool { - // Config values pulled from CoreCLR private class HillClimbing { + // Config values pulled from CoreCLR + // TODO: Move to runtime configuration variables. public static HillClimbing ThreadPoolHillClimber { get; } = new HillClimbing(4, 20, 100 / 100.0, 8, 15 / 100.0, 300 / 100.0, 4, 20, 10, 200, 1 / 100.0, 200 / 100.0, 15 / 100.0); - public enum State + public enum StateOrTransition { Warmup, Initializing, @@ -73,28 +76,28 @@ public HillClimbing(int wavePeriod, int maxWaveMagnitude, double waveMagnitudeMu _samples = new double[_samplesToMeasure]; _threadCounts = new double[_samplesToMeasure]; - _currentSampleInterval = _randomIntervalGenerator.Next(_sampleIntervalLow, _sampleIntervalHigh); + _currentSampleInterval = _randomIntervalGenerator.Next(_sampleIntervalLow, _sampleIntervalHigh + 1); } - public (int newThreadCount, int newSampleInterval) Update(int currentThreadCount, double sampleDuration, int numCompletions) + public (int newThreadCount, int newSampleInterval) Update(int currentThreadCount, double sampleDurationSeconds, int numCompletions) { // // If someone changed the thread count without telling us, update our records accordingly. // if (currentThreadCount != _lastThreadCount) - ForceChange(currentThreadCount, State.Initializing); + ForceChange(currentThreadCount, StateOrTransition.Initializing); // // Update the cumulative stats for this thread count // - _secondsElapsedSinceLastChange += sampleDuration; + _secondsElapsedSinceLastChange += sampleDurationSeconds; _completionsSinceLastChange += numCompletions; // // Add in any data we've already collected about this sample // - sampleDuration += _accumulatedSampleDuration; + sampleDurationSeconds += _accumulatedSampleDuration; numCompletions += _accumulatedCompletionCount; // @@ -122,7 +125,7 @@ public HillClimbing(int wavePeriod, int maxWaveMagnitude, double waveMagnitudeMu { // not accurate enough yet. Let's accumulate the data so far, and tell the ThreadPool // to collect a little more. - _accumulatedSampleDuration = sampleDuration; + _accumulatedSampleDuration = sampleDurationSeconds; _accumulatedCompletionCount = numCompletions; return (currentThreadCount, 10); } @@ -136,8 +139,8 @@ public HillClimbing(int wavePeriod, int maxWaveMagnitude, double waveMagnitudeMu // // Add the current thread count and throughput sample to our history // - double throughput = numCompletions / sampleDuration; - //Worker Thread Adjustment Sample event + double throughput = numCompletions / sampleDurationSeconds; + //TODO: Event: Worker Thread Adjustment Sample int sampleIndex = _totalSamples % _samplesToMeasure; _samples[sampleIndex] = throughput; @@ -153,7 +156,7 @@ public HillClimbing(int wavePeriod, int maxWaveMagnitude, double waveMagnitudeMu (double real, double imaginary) ratio = (0, 0); double confidence = 0; - State state = State.Warmup; + StateOrTransition state = StateOrTransition.Warmup; // // How many samples will we use? It must be at least the three wave periods we're looking for, and it must also be a whole @@ -224,12 +227,12 @@ public HillClimbing(int wavePeriod, int maxWaveMagnitude, double waveMagnitudeMu // ratio.real = (throughputWaveComponent.real - (_targetThroughputRatio * threadWaveComponent.real)) / threadWaveComponent.real; ratio.imaginary = (throughputWaveComponent.imaginary - (_targetThroughputRatio * threadWaveComponent.imaginary)) / threadWaveComponent.imaginary; - state = State.ClimbingMove; + state = StateOrTransition.ClimbingMove; } else { ratio = (0, 0); - state = State.Stabilizing; + state = StateOrTransition.Stabilizing; } // @@ -265,7 +268,7 @@ public HillClimbing(int wavePeriod, int maxWaveMagnitude, double waveMagnitudeMu // are enhanced. This allows us to move quickly if we're far away from the target, but more slowly // if we're getting close, giving us rapid ramp-up without wild oscillations around the target. // - double gain = _maxChangePerSecond * sampleDuration; + double gain = _maxChangePerSecond * sampleDurationSeconds; move = Math.Pow(Math.Abs(move), _gainExponent) * (move >= 0.0 ? 1 : -1) * gain; move = Math.Min(move, _maxChangePerSample); @@ -312,7 +315,7 @@ public HillClimbing(int wavePeriod, int maxWaveMagnitude, double waveMagnitudeMu // Record these numbers for posterity // - // Worker Thread Adjustment stats event + // TODO: Event: Worker Thread Adjustment stats // @@ -338,22 +341,22 @@ public HillClimbing(int wavePeriod, int maxWaveMagnitude, double waveMagnitudeMu return (newThreadCount, newSampleInterval); } - private void ChangeThreadCount(int newThreadCount, State state) + private void ChangeThreadCount(int newThreadCount, StateOrTransition state) { _lastThreadCount = newThreadCount; - _currentSampleInterval = _randomIntervalGenerator.Next(_sampleIntervalLow, _sampleIntervalHigh); + _currentSampleInterval = _randomIntervalGenerator.Next(_sampleIntervalLow, _sampleIntervalHigh + 1); double throughput = _secondsElapsedSinceLastChange > 0 ? _completionsSinceLastChange / _secondsElapsedSinceLastChange : 0; LogTransition(newThreadCount, throughput, state); _secondsElapsedSinceLastChange = 0; _completionsSinceLastChange = 0; } - private void LogTransition(int newThreadCount, double throughput, State state) + private void LogTransition(int newThreadCount, double throughput, StateOrTransition state) { // TODO: Log transitions } - public void ForceChange(int newThreadCount, State state) + public void ForceChange(int newThreadCount, StateOrTransition state) { if(_lastThreadCount != newThreadCount) { @@ -364,11 +367,20 @@ public void ForceChange(int newThreadCount, State state) private static (double real, double imaginary) GetWaveComponent(double[] samples, int numSamples, double period) { + Debug.Assert(numSamples >= period); // can't measure a wave that doesn't fit + Debug.Assert(period >= 2); // can't measure above the Nyquist frequency + Debug.Assert(numSamples <= samples.Length); // can't measure more samples than we have + + // + // Calculate the sinusoid with the given period. + // We're using the Goertzel algorithm for this. See http://en.wikipedia.org/wiki/Goertzel_algorithm. + // + double w = 2 * Math.PI / period; double cos = Math.Cos(w); double coeff = 2 * cos; double q0 = 0, q1 = 0, q2 = 0; - for(int i = 0; i < numSamples && i < samples.Length; ++i) + for(int i = 0; i < numSamples; ++i) { q0 = coeff * q1 - q2 + samples[i]; q2 = q1; From 2e5dc38136284b8c24b6ec8eb3ac483dee8dbd49 Mon Sep 17 00:00:00 2001 From: t-jekor Date: Tue, 6 Jun 2017 11:52:32 -0700 Subject: [PATCH 10/13] Fixed algorithm logic error --- .../src/System/Threading/ClrThreadPool.HillClimbing.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.cs b/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.cs index 6b8d04993da..050af44b208 100644 --- a/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.cs +++ b/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.cs @@ -386,7 +386,7 @@ private static (double real, double imaginary) GetWaveComponent(double[] samples q2 = q1; q1 = q0; } - return ((q1 - q2 * cos) / samples.Length, (q2 * Math.Sin(w)) / samples.Length); + return ((q1 - q2 * cos) / numSamples, (q2 * Math.Sin(w)) / numSamples); } private static double Abs((double real, double imaginary) complex) From 53e330fc4483739291316504525b045514c91f68 Mon Sep 17 00:00:00 2001 From: t-jekor Date: Tue, 6 Jun 2017 14:10:04 -0700 Subject: [PATCH 11/13] More PR feedback --- .../Threading/ClrThreadPool.HillClimbing.cs | 80 +++++++++++-------- .../src/System/Threading/ClrThreadPool.cs | 2 +- 2 files changed, 49 insertions(+), 33 deletions(-) diff --git a/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.cs b/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.cs index 050af44b208..f048ed8b3e2 100644 --- a/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.cs +++ b/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.cs @@ -11,6 +11,32 @@ internal static partial class ClrThreadPool private class HillClimbing { + private struct Complex + { + public Complex(double real, double imaginary) + { + Real = real; + Imaginary = imaginary; + } + + public double Imaginary { get; } + public double Real { get; } + + public static Complex operator*(double scalar, Complex complex) => new Complex(scalar * complex.Real, scalar * complex.Imaginary); + + public static Complex operator/(Complex complex, double scalar) => new Complex(complex.Real / scalar, complex.Imaginary / scalar); + + public static Complex operator-(Complex lhs, Complex rhs) => new Complex(lhs.Real - rhs.Real, lhs.Imaginary - rhs.Imaginary); + + public static Complex operator/(Complex lhs, Complex rhs) + { + double denom = rhs.Real * rhs.Real + rhs.Imaginary * rhs.Imaginary; + return new Complex((lhs.Real * rhs.Real + lhs.Imaginary * rhs.Imaginary) / denom, (-lhs.Real * rhs.Imaginary + lhs.Imaginary * rhs.Real) / denom); + } + + public static double Abs(Complex complex) => Math.Sqrt(complex.Real * complex.Real + complex.Imaginary * complex.Imaginary); + } + // Config values pulled from CoreCLR // TODO: Move to runtime configuration variables. public static HillClimbing ThreadPoolHillClimber { get; } = new HillClimbing(4, 20, 100 / 100.0, 8, 15 / 100.0, 300 / 100.0, 4, 20, 10, 200, 1 / 100.0, 200 / 100.0, 15 / 100.0); @@ -48,7 +74,7 @@ public enum StateOrTransition private double _secondsElapsedSinceLastChange; private double _completionsSinceLastChange; private int _accumulatedCompletionCount; - private double _accumulatedSampleDuration; + private double _accumulatedSampleDurationSeconds; private double[] _samples; private double[] _threadCounts; private int _currentSampleInterval; @@ -97,7 +123,7 @@ public HillClimbing(int wavePeriod, int maxWaveMagnitude, double waveMagnitudeMu // // Add in any data we've already collected about this sample // - sampleDurationSeconds += _accumulatedSampleDuration; + sampleDurationSeconds += _accumulatedSampleDurationSeconds; numCompletions += _accumulatedCompletionCount; // @@ -125,7 +151,7 @@ public HillClimbing(int wavePeriod, int maxWaveMagnitude, double waveMagnitudeMu { // not accurate enough yet. Let's accumulate the data so far, and tell the ThreadPool // to collect a little more. - _accumulatedSampleDuration = sampleDurationSeconds; + _accumulatedSampleDurationSeconds = sampleDurationSeconds; _accumulatedCompletionCount = numCompletions; return (currentThreadCount, 10); } @@ -133,14 +159,14 @@ public HillClimbing(int wavePeriod, int maxWaveMagnitude, double waveMagnitudeMu // // We've got enouugh data for our sample; reset our accumulators for next time. // - _accumulatedSampleDuration = 0; + _accumulatedSampleDurationSeconds = 0; _accumulatedCompletionCount = 0; // // Add the current thread count and throughput sample to our history // double throughput = numCompletions / sampleDurationSeconds; - //TODO: Event: Worker Thread Adjustment Sample + // TODO: Event: Worker Thread Adjustment Sample int sampleIndex = _totalSamples % _samplesToMeasure; _samples[sampleIndex] = throughput; @@ -150,10 +176,10 @@ public HillClimbing(int wavePeriod, int maxWaveMagnitude, double waveMagnitudeMu // // Set up defaults for our metrics // - (double real, double imaginary) threadWaveComponent = (0, 0); - (double real, double imaginary) throughputWaveComponent = (0, 0); + Complex threadWaveComponent = default(Complex); + Complex throughputWaveComponent = default(Complex); double throughputErrorEstimate = 0; - (double real, double imaginary) ratio = (0, 0); + Complex ratio = default(Complex); double confidence = 0; StateOrTransition state = StateOrTransition.Warmup; @@ -194,22 +220,18 @@ public HillClimbing(int wavePeriod, int maxWaveMagnitude, double waveMagnitudeMu // throughput). Our "error" estimate (the amount of noise that might be present in the // frequency band we're really interested in) is the average of the adjacent bands. // - throughputWaveComponent = GetWaveComponent(_samples, sampleCount, _wavePeriod); - throughputWaveComponent = (throughputWaveComponent.real / averageThroughput, throughputWaveComponent.imaginary / averageThroughput); - (double real, double imaginary) intermediateErrorEstimate = GetWaveComponent(_samples, sampleCount, adjacentPeriod1); - throughputErrorEstimate = Abs((intermediateErrorEstimate.real / averageThroughput, intermediateErrorEstimate.imaginary / averageThroughput)); + throughputWaveComponent = GetWaveComponent(_samples, sampleCount, _wavePeriod) / averageThroughput; + throughputErrorEstimate = Complex.Abs(GetWaveComponent(_samples, sampleCount, adjacentPeriod1) / averageThroughput); if (adjacentPeriod2 <= sampleCount) { - intermediateErrorEstimate = GetWaveComponent(_samples, sampleCount, adjacentPeriod2); - throughputErrorEstimate = Math.Max(throughputErrorEstimate, Abs((intermediateErrorEstimate.real / averageThroughput, intermediateErrorEstimate.imaginary / averageThroughput))); + throughputErrorEstimate = Math.Max(throughputErrorEstimate, Complex.Abs(GetWaveComponent(_samples, sampleCount, adjacentPeriod2) / averageThroughput)); } // // Do the same for the thread counts, so we have something to compare to. We don't measure thread count // noise, because there is none; these are exact measurements. // - (double real, double imaginary) intermediateThreadWaveComponent = GetWaveComponent(_threadCounts, sampleCount, _wavePeriod); - threadWaveComponent = (intermediateThreadWaveComponent.real / averageThreadCount, intermediateThreadWaveComponent.imaginary / averageThreadCount); + threadWaveComponent = GetWaveComponent(_threadCounts, sampleCount, _wavePeriod) / averageThreadCount; // // Update our moving average of the throughput noise. We'll use this later as feedback to @@ -220,18 +242,17 @@ public HillClimbing(int wavePeriod, int maxWaveMagnitude, double waveMagnitudeMu else _averageThroughputNoise = (_throughputErrorSmoothingFactor * throughputErrorEstimate) + ((1.0 - _throughputErrorSmoothingFactor) * _averageThroughputNoise); - if (Abs(threadWaveComponent) > 0) + if (Complex.Abs(threadWaveComponent) > 0) { // // Adjust the throughput wave so it's centered around the target wave, and then calculate the adjusted throughput/thread ratio. // - ratio.real = (throughputWaveComponent.real - (_targetThroughputRatio * threadWaveComponent.real)) / threadWaveComponent.real; - ratio.imaginary = (throughputWaveComponent.imaginary - (_targetThroughputRatio * threadWaveComponent.imaginary)) / threadWaveComponent.imaginary; + ratio = (throughputWaveComponent - (_targetThroughputRatio * threadWaveComponent)) / threadWaveComponent; state = StateOrTransition.ClimbingMove; } else { - ratio = (0, 0); + ratio = new Complex(0, 0); state = StateOrTransition.Stabilizing; } @@ -241,7 +262,7 @@ public HillClimbing(int wavePeriod, int maxWaveMagnitude, double waveMagnitudeMu // double noiseForConfidence = Math.Max(_averageThroughputNoise, throughputErrorEstimate); if (noiseForConfidence > 0) - confidence = (Abs(threadWaveComponent) / noiseForConfidence) / _targetSignalToNoiseRatio; + confidence = (Complex.Abs(threadWaveComponent) / noiseForConfidence) / _targetSignalToNoiseRatio; else confidence = 1.0; //there is no noise! @@ -256,7 +277,7 @@ public HillClimbing(int wavePeriod, int maxWaveMagnitude, double waveMagnitudeMu // If they're 90 degrees out of phase, we won't move at all, because we can't tell wether we're // having a negative or positive effect on throughput. // - double move = Math.Min(1.0, Math.Max(-1.0, ratio.real)); + double move = Math.Min(1.0, Math.Max(-1.0, ratio.Real)); // // Apply our confidence multiplier. @@ -333,8 +354,8 @@ public HillClimbing(int wavePeriod, int maxWaveMagnitude, double waveMagnitudeMu // we'll simply stay at minThreads much longer, and only occasionally try a higher value. // int newSampleInterval; - if (ratio.real < 0.0 && newThreadCount == minThreads) - newSampleInterval = (int)(0.5 + _currentSampleInterval * (10.0 * Math.Max(-ratio.real, 1.0))); + if (ratio.Real < 0.0 && newThreadCount == minThreads) + newSampleInterval = (int)(0.5 + _currentSampleInterval * (10.0 * Math.Max(-ratio.Real, 1.0))); else newSampleInterval = _currentSampleInterval; @@ -365,7 +386,7 @@ public void ForceChange(int newThreadCount, StateOrTransition state) } } - private static (double real, double imaginary) GetWaveComponent(double[] samples, int numSamples, double period) + private Complex GetWaveComponent(double[] samples, int numSamples, double period) { Debug.Assert(numSamples >= period); // can't measure a wave that doesn't fit Debug.Assert(period >= 2); // can't measure above the Nyquist frequency @@ -382,16 +403,11 @@ private static (double real, double imaginary) GetWaveComponent(double[] samples double q0 = 0, q1 = 0, q2 = 0; for(int i = 0; i < numSamples; ++i) { - q0 = coeff * q1 - q2 + samples[i]; + q0 = coeff * q1 - q2 + samples[(_totalSamples - numSamples + i) % _samplesToMeasure]; q2 = q1; q1 = q0; } - return ((q1 - q2 * cos) / numSamples, (q2 * Math.Sin(w)) / numSamples); - } - - private static double Abs((double real, double imaginary) complex) - { - return Math.Sqrt(complex.real * complex.real + complex.imaginary * complex.imaginary); + return new Complex((q1 - q2 * cos) / numSamples, (q2 * Math.Sin(w)) / numSamples); } } } diff --git a/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.cs b/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.cs index 3ad0abc9f2f..1a8b834b598 100644 --- a/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.cs +++ b/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.cs @@ -31,7 +31,7 @@ public static bool SetMinThreads(int threads) public static bool SetMaxThreads(int threads) { - if (threads < ThreadPoolGlobals.processorCount || threads < s_minThreads) + if (threads < s_minThreads || threads == 0) { return false; } From 6cd9aa05f4fca689a19700575aef0ccc7aca72e6 Mon Sep 17 00:00:00 2001 From: t-jekor Date: Thu, 8 Jun 2017 11:06:38 -0700 Subject: [PATCH 12/13] PR feedback on complex and file headers. --- .../src/System.Private.CoreLib.csproj | 1 + .../ClrThreadPool.HillClimbing.Complex.cs | 42 +++++++++++++++++++ .../Threading/ClrThreadPool.HillClimbing.cs | 38 +++-------------- .../src/System/Threading/ClrThreadPool.cs | 5 ++- 4 files changed, 52 insertions(+), 34 deletions(-) create mode 100644 src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.Complex.cs diff --git a/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj b/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj index 42aa8c884c2..3bd0848c687 100644 --- a/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj +++ b/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj @@ -649,6 +649,7 @@ + diff --git a/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.Complex.cs b/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.Complex.cs new file mode 100644 index 00000000000..7be30ed65ce --- /dev/null +++ b/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.Complex.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + + +namespace System.Threading +{ + internal static partial class ClrThreadPool + { + + private partial class HillClimbing + { + private struct Complex + { + public Complex(double real, double imaginary) + { + Real = real; + Imaginary = imaginary; + } + + public double Imaginary { get; } + public double Real { get; } + + public static Complex operator*(double scalar, Complex complex) => new Complex(scalar * complex.Real, scalar * complex.Imaginary); + + public static Complex operator*(Complex complex, double scalar) => scalar * complex; + + public static Complex operator/(Complex complex, double scalar) => new Complex(complex.Real / scalar, complex.Imaginary / scalar); + + public static Complex operator-(Complex lhs, Complex rhs) => new Complex(lhs.Real - rhs.Real, lhs.Imaginary - rhs.Imaginary); + + public static Complex operator/(Complex lhs, Complex rhs) + { + double denom = rhs.Real * rhs.Real + rhs.Imaginary * rhs.Imaginary; + return new Complex((lhs.Real * rhs.Real + lhs.Imaginary * rhs.Imaginary) / denom, (-lhs.Real * rhs.Imaginary + lhs.Imaginary * rhs.Real) / denom); + } + + public double Abs() => Math.Sqrt(Real * Real + Imaginary * Imaginary); + } + } + } +} diff --git a/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.cs b/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.cs index f048ed8b3e2..5c51cd42972 100644 --- a/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.cs +++ b/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.cs @@ -9,34 +9,8 @@ namespace System.Threading internal static partial class ClrThreadPool { - private class HillClimbing + private partial class HillClimbing { - private struct Complex - { - public Complex(double real, double imaginary) - { - Real = real; - Imaginary = imaginary; - } - - public double Imaginary { get; } - public double Real { get; } - - public static Complex operator*(double scalar, Complex complex) => new Complex(scalar * complex.Real, scalar * complex.Imaginary); - - public static Complex operator/(Complex complex, double scalar) => new Complex(complex.Real / scalar, complex.Imaginary / scalar); - - public static Complex operator-(Complex lhs, Complex rhs) => new Complex(lhs.Real - rhs.Real, lhs.Imaginary - rhs.Imaginary); - - public static Complex operator/(Complex lhs, Complex rhs) - { - double denom = rhs.Real * rhs.Real + rhs.Imaginary * rhs.Imaginary; - return new Complex((lhs.Real * rhs.Real + lhs.Imaginary * rhs.Imaginary) / denom, (-lhs.Real * rhs.Imaginary + lhs.Imaginary * rhs.Real) / denom); - } - - public static double Abs(Complex complex) => Math.Sqrt(complex.Real * complex.Real + complex.Imaginary * complex.Imaginary); - } - // Config values pulled from CoreCLR // TODO: Move to runtime configuration variables. public static HillClimbing ThreadPoolHillClimber { get; } = new HillClimbing(4, 20, 100 / 100.0, 8, 15 / 100.0, 300 / 100.0, 4, 20, 10, 200, 1 / 100.0, 200 / 100.0, 15 / 100.0); @@ -221,10 +195,10 @@ public HillClimbing(int wavePeriod, int maxWaveMagnitude, double waveMagnitudeMu // frequency band we're really interested in) is the average of the adjacent bands. // throughputWaveComponent = GetWaveComponent(_samples, sampleCount, _wavePeriod) / averageThroughput; - throughputErrorEstimate = Complex.Abs(GetWaveComponent(_samples, sampleCount, adjacentPeriod1) / averageThroughput); + throughputErrorEstimate = (GetWaveComponent(_samples, sampleCount, adjacentPeriod1) / averageThroughput).Abs(); if (adjacentPeriod2 <= sampleCount) { - throughputErrorEstimate = Math.Max(throughputErrorEstimate, Complex.Abs(GetWaveComponent(_samples, sampleCount, adjacentPeriod2) / averageThroughput)); + throughputErrorEstimate = Math.Max(throughputErrorEstimate, (GetWaveComponent(_samples, sampleCount, adjacentPeriod2) / averageThroughput).Abs()); } // @@ -242,7 +216,7 @@ public HillClimbing(int wavePeriod, int maxWaveMagnitude, double waveMagnitudeMu else _averageThroughputNoise = (_throughputErrorSmoothingFactor * throughputErrorEstimate) + ((1.0 - _throughputErrorSmoothingFactor) * _averageThroughputNoise); - if (Complex.Abs(threadWaveComponent) > 0) + if (threadWaveComponent.Abs() > 0) { // // Adjust the throughput wave so it's centered around the target wave, and then calculate the adjusted throughput/thread ratio. @@ -262,7 +236,7 @@ public HillClimbing(int wavePeriod, int maxWaveMagnitude, double waveMagnitudeMu // double noiseForConfidence = Math.Max(_averageThroughputNoise, throughputErrorEstimate); if (noiseForConfidence > 0) - confidence = (Complex.Abs(threadWaveComponent) / noiseForConfidence) / _targetSignalToNoiseRatio; + confidence = (threadWaveComponent.Abs() / noiseForConfidence) / _targetSignalToNoiseRatio; else confidence = 1.0; //there is no noise! @@ -407,7 +381,7 @@ private Complex GetWaveComponent(double[] samples, int numSamples, double period q2 = q1; q1 = q0; } - return new Complex((q1 - q2 * cos) / numSamples, (q2 * Math.Sin(w)) / numSamples); + return new Complex(q1 - q2 * cos, q2 * Math.Sin(w)) / numSamples; } } } diff --git a/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.cs b/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.cs index 1a8b834b598..0e56931ae3b 100644 --- a/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.cs +++ b/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.cs @@ -1,5 +1,6 @@ -using System.Diagnostics; -using System.Runtime.InteropServices; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. namespace System.Threading { From 180c21a41957e34170b0b33b7cf061d10d9d1409 Mon Sep 17 00:00:00 2001 From: t-jekor Date: Thu, 8 Jun 2017 11:20:31 -0700 Subject: [PATCH 13/13] Added comment referencing @mattwarren's article on the algorithm. --- .../src/System/Threading/ClrThreadPool.HillClimbing.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.cs b/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.cs index 5c51cd42972..909a2fd725f 100644 --- a/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.cs +++ b/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.cs @@ -8,7 +8,7 @@ namespace System.Threading { internal static partial class ClrThreadPool { - + // The Hill Climbing algorithm is described at http://mattwarren.org/2017/04/13/The-CLR-Thread-Pool-Thread-Injection-Algorithm/ private partial class HillClimbing { // Config values pulled from CoreCLR