Skip to content
This repository was archived by the owner on Nov 1, 2020. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 0 additions & 86 deletions src/Native/System.Private.CoreLib.Native/pal_time.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,89 +141,3 @@ extern "C" uint64_t CoreLibNative_GetTickCount64()
#endif
return retval;
}


struct ProcessCpuInformation
{
uint64_t lastRecordedCurrentTime;
uint64_t lastRecordedKernelTime;
uint64_t lastRecordedUserTime;
};


/*
Function:
CoreLibNative_GetCpuUtilization

The main purpose of this function is to compute the overall CPU utilization
for the CLR thread pool to regulate the number of worker threads.
Since there is no consistent API on Unix to get the CPU utilization
from a user process, getrusage and gettimeofday are used to
compute the current process's CPU utilization instead.

*/

static long numProcessors = 0;
extern "C" int32_t CoreLibNative_GetCpuUtilization(ProcessCpuInformation* previousCpuInfo)
{
if (numProcessors <= 0)
{
numProcessors = sysconf(_SC_NPROCESSORS_CONF);
if (numProcessors <= 0)
{
return 0;
}
}

uint64_t kernelTime = 0;
uint64_t userTime = 0;

struct rusage resUsage;
if (getrusage(RUSAGE_SELF, &resUsage) == -1)
{
assert(false);
return 0;
}
else
{
kernelTime = TimeValToNanoseconds(resUsage.ru_stime);
userTime = TimeValToNanoseconds(resUsage.ru_utime);
}

uint64_t currentTime = CoreLibNative_GetHighPrecisionCount() * NanosecondsPerSecond / CoreLibNative_GetHighPrecisionCounterFrequency();

uint64_t lastRecordedCurrentTime = previousCpuInfo->lastRecordedCurrentTime;
uint64_t lastRecordedKernelTime = previousCpuInfo->lastRecordedKernelTime;
uint64_t lastRecordedUserTime = previousCpuInfo->lastRecordedUserTime;

uint64_t cpuTotalTime = 0;
if (currentTime > lastRecordedCurrentTime)
{
// cpuTotalTime is based on clock time. Since multiple threads can run in parallel,
// we need to scale cpuTotalTime cover the same amount of total CPU time.
// rusage time is already scaled across multiple processors.
cpuTotalTime = (currentTime - lastRecordedCurrentTime);
cpuTotalTime *= numProcessors;
}

uint64_t cpuBusyTime = 0;
if (userTime >= lastRecordedUserTime && kernelTime >= lastRecordedKernelTime)
{
cpuBusyTime = (userTime - lastRecordedUserTime) + (kernelTime - lastRecordedKernelTime);
}

int32_t cpuUtilization = 0;
if (cpuTotalTime > 0 && cpuBusyTime > 0)
{
cpuUtilization = static_cast<int32_t>(cpuBusyTime / cpuTotalTime);
}

assert(cpuUtilization >= 0 && cpuUtilization <= 100);

previousCpuInfo->lastRecordedCurrentTime = currentTime;
previousCpuInfo->lastRecordedUserTime = userTime;
previousCpuInfo->lastRecordedKernelTime = kernelTime;

return cpuUtilization;
}

Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ internal struct ProcessCpuInformation
ulong lastRecordedUserTime;
}

[DllImport(Libraries.CoreLibNative, EntryPoint = "CoreLibNative_GetCpuUtilization")]
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetCpuUtilization")]
internal static extern unsafe int GetCpuUtilization(ref ProcessCpuInformation previousCpuInfo);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1110,6 +1110,24 @@
<Compile Include="$(MSBuildThisFileDirectory)System\Security\SecureString.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\TimeZoneInfo.Unix.cs" />
</ItemGroup>
<ItemGroup Condition="'$(FeaturePortableThreadPool)' == 'true'">
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadPool.Portable.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\ClrThreadPool.cs" />
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Little bit confusing naming here, is ClrThreadPool equal to ThreadPool.Portable ?

Copy link
Copy Markdown
Member Author

@filipnavara filipnavara Jan 29, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kept the original naming of the files. There's ThreadPool.Portable.cs which implements the ThreadPool methods. The other files are the internal implementation classes, which are created and called from the ThreadPool.Portable.cs code.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume the naming comes from an implementation inside CLR vs. Win32 Thread Pool API (https://docs.microsoft.com/en-us/windows/desktop/procthread/thread-pool-api).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer to follow the existing naming convention. ThreadPool.cs for portable code, ThreadPool.CoreCLR.cs for CoreCLR runtime specific implementation

Copy link
Copy Markdown
Member Author

@filipnavara filipnavara Jan 29, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is already ThreadPool.cs shared between all implementations. Then there are three implementations of the actual thread pool (CoreCLR w/ unmanaged code and PAL, CortRT/Portable, CoreRT/Windows).

This moves one of the implementations (CoreRT/Portable) under feature flag to shared partition. This implementation is managed reimplementation of what CoreCLR does and it's currently used by CoreRT on Unix.

Copy link
Copy Markdown
Member Author

@filipnavara filipnavara Jan 29, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea was to use the CoreRT implementation in Mono by simply adding

    <FeaturePortableThreadPool>true</FeaturePortableThreadPool>
    <FeaturePortableTimer>true</FeaturePortableTimer>

to Mono's System.Private.CoreLib.csproj. It could eventually be used in CoreCLR too, but that will require a lot of performance testing and changes to unmanaged code that are currently not feasible.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it helps, it would be ok to create PortableThreadPool subdirectory and move the portable threadpool implementation there.

Copy link
Copy Markdown
Member

@stephentoub stephentoub Feb 6, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it helps, it would be ok to create PortableThreadPool subdirectory and move the portable threadpool implementation there.

And rename ClrThreadPool.*.cs to PortableThreadPool.*.cs, presumably, or something along those lines?

<Compile Include="$(MSBuildThisFileDirectory)System\Threading\ClrThreadPoolEventSource.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\ClrThreadPool.GateThread.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\ClrThreadPool.HillClimbing.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\ClrThreadPool.HillClimbing.Complex.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\ClrThreadPool.ThreadCounts.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\ClrThreadPool.WaitThread.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\ClrThreadPool.WorkerThread.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\ClrThreadPool.CpuUtilizationReader.Unix.cs" Condition="'$(TargetsUnix)' == 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\ClrThreadPool.CpuUtilizationReader.Windows.cs" Condition="'$(TargetsWindows)'=='true'" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Native\Interop.GetCpuUtilization.cs" Condition="'$(TargetsUnix)'=='true'" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.GetSystemTimes.cs" Condition="'$(TargetsWindows)' == 'true'" />
</ItemGroup>
<ItemGroup Condition="'$(FeaturePortableTimer)' == 'true'">
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\Timer.Portable.cs" />
</ItemGroup>
<ItemGroup Condition="'$(FeatureHardwareIntrinsics)' == 'true' AND ('$(Platform)' == 'x64' OR ('$(Platform)' == 'x86' AND '$(TargetsUnix)' != 'true'))">
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Intrinsics\X86\Aes.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Intrinsics\X86\Avx.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@ private static class GateThread

private static int s_runningState;

private static AutoResetEvent s_runGateThreadEvent = new AutoResetEvent(true);

private static LowLevelLock s_createdLock = new LowLevelLock();
private static readonly AutoResetEvent s_runGateThreadEvent = new AutoResetEvent(true);

private static readonly CpuUtilizationReader s_cpu = new CpuUtilizationReader();
private const int MaxRuns = 2;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,23 @@ internal partial class ClrThreadPool
private WaitThreadNode _waitThreadsHead;
private WaitThreadNode _waitThreadsTail;

private LowLevelLock _waitThreadLock = new LowLevelLock();
#if CORERT
private readonly Lock _waitThreadLock = new Lock();
#else
private object _waitThreadLock = new object();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can create class LowLevelLock : object with a couple of methods on it to make these ifdefs unnecessary.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I considered it as an option. It would likely result in some counter-part like [LowLevel]LockHolder that can be used in using (new LockHolder(foo)). There's only few places so #if's seemed like an option for now, but I am going to revisit the options before dropping the "WIP" tag.

Copy link
Copy Markdown
Member Author

@filipnavara filipnavara Jan 29, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm specifically going to evaluate the performance of LowLevelLock (only used in two places; has the Windows PInvoke-heavy implementation that is not used anywhere else) vs. Lock (used to implement Monitor, so should be pretty optimized) vs. lock (obj) (portable, but goes through one extra hoop through ObjectHeader/SyncTable; the impact on the specific places might be negligible) to see whether there is any merit preferring one over the others.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implementation-wise, Lock in CoreRT is very roughly based on the old implementation of Monitor with some major differences. It is quite likely that currently it is not as good as Monitor's current implementation in CoreCLR. Eventually that implementation would also have to be ported to CoreRT. In any case, see my other comment for my suggestions.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LowLevelLock (only used in two places; has the Windows PInvoke-heavy implementation that is not used anywhere else)

LowLevelLock does some (tiny) bit of spin-waiting and where it is used for the most part the locks are typically uncontended, so it's not really p/invoke-heavy in practice (if it turns out to be, the spin-waiting strategy could be improved to fix that).

Copy link
Copy Markdown
Member Author

@filipnavara filipnavara Jan 31, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The motivation behind this change was two-fold:

  • Avoid bringing LowLevelLock/LowLevelMonitor to shared CoreLib (and all the dependent code).
  • Use the same primitive as the lock (...) pattern. The premise was that this is what user code gets to use and it should be the one that is optimized better.

The problem is that it violates the condition that it is non-interruptible. Another problem is that I grossly underestimated the dependencies of LowLevelLifoSemaphore, so in my revised plan I would bring the dependencies to shared code anyway and this change would become unnecessary.

#endif

/// <summary>
/// Register a wait handle on a <see cref="WaitThread"/>.
/// </summary>
/// <param name="handle">A description of the requested registration.</param>
internal void RegisterWaitHandle(RegisteredWaitHandle handle)
{
_waitThreadLock.Acquire();
try
#if CORERT
using (LockHolder.Hold(_waitThreadLock))
#else
lock (_waitThreadLock)
#endif
{
if (_waitThreadsHead == null) // Lazily create the first wait thread.
{
Expand Down Expand Up @@ -56,10 +63,6 @@ internal void RegisterWaitHandle(RegisteredWaitHandle handle)
prev.Next.Thread.RegisterWaitHandle(handle);
return;
}
finally
{
_waitThreadLock.Release();
}
}

/// <summary>
Expand All @@ -69,19 +72,18 @@ internal void RegisterWaitHandle(RegisteredWaitHandle handle)
/// <returns><c>true</c> if the thread was successfully removed; otherwise, <c>false</c></returns>
private bool TryRemoveWaitThread(WaitThread thread)
{
_waitThreadLock.Acquire();
try
#if CORERT
using (LockHolder.Hold(_waitThreadLock))
#else
lock (_waitThreadLock)
#endif
{
if (thread.AnyUserWaits)
{
return false;
}
RemoveWaitThread(thread);
}
finally
{
_waitThreadLock.Release();
}
return true;
}

Expand Down Expand Up @@ -267,8 +269,11 @@ private void WaitThreadStart()
/// </summary>
private void ProcessRemovals()
{
ThreadPoolInstance._waitThreadLock.Acquire();
try
#if CORERT
using (LockHolder.Hold(ThreadPoolInstance._waitThreadLock))
#else
lock (ThreadPoolInstance._waitThreadLock)
#endif
{
Debug.Assert(_numPendingRemoves >= 0);
Debug.Assert(_numPendingRemoves <= _pendingRemoves.Length);
Expand Down Expand Up @@ -307,10 +312,6 @@ private void ProcessRemovals()
Debug.Assert(originalNumUserWaits - originalNumPendingRemoves == _numUserWaits,
$"{originalNumUserWaits} - {originalNumPendingRemoves} == {_numUserWaits}");
}
finally
{
ThreadPoolInstance._waitThreadLock.Release();
}
}

/// <summary>
Expand Down Expand Up @@ -350,7 +351,6 @@ private void CompleteWait(object state)
/// <returns>If the handle was successfully registered on this wait thread.</returns>
public bool RegisterWaitHandle(RegisteredWaitHandle handle)
{
ThreadPoolInstance._waitThreadLock.VerifyIsLocked();
if (_numUserWaits == WaitHandle.MaxWaitHandles - 1)
{
return false;
Expand Down Expand Up @@ -389,8 +389,11 @@ private void UnregisterWait(RegisteredWaitHandle handle, bool blocking)
{
bool pendingRemoval = false;
// TODO: Optimization: Try to unregister wait directly if it isn't being waited on.
ThreadPoolInstance._waitThreadLock.Acquire();
try
#if CORERT
using (LockHolder.Hold(ThreadPoolInstance._waitThreadLock))
#else
lock (ThreadPoolInstance._waitThreadLock)
#endif
{
// If this handle is not already pending removal and hasn't already been removed
if (Array.IndexOf(_registeredWaits, handle) != -1 && Array.IndexOf(_pendingRemoves, handle) == -1)
Expand All @@ -400,10 +403,6 @@ private void UnregisterWait(RegisteredWaitHandle handle, bool blocking)
pendingRemoval = true;
}
}
finally
{
ThreadPoolInstance._waitThreadLock.Release();
}

if (blocking)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information.

using System.Globalization;
using Internal.LowLevelLinq;
using Internal.Runtime.Augments;

namespace System.Threading
Expand All @@ -19,7 +18,7 @@ private static class WorkerThread
/// Semaphore for controlling how many threads are currently working.
/// </summary>
private static LowLevelLifoSemaphore s_semaphore = new LowLevelLifoSemaphore(0, MaxPossibleThreadCount);

private static void WorkerThreadStart()
{
ClrThreadPoolEventSource.Log.WorkerThreadStart(ThreadCounts.VolatileReadCounts(ref ThreadPoolInstance._separated.counts).numExistingThreads);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@ internal sealed partial class ClrThreadPool

private short _minThreads;
private short _maxThreads;
private readonly LowLevelLock _maxMinThreadLock = new LowLevelLock();
#if CORERT
private readonly Lock _maxMinThreadLock = new Lock();
#else
private readonly object _maxMinThreadLock = new object();
#endif

[StructLayout(LayoutKind.Explicit, Size = CacheLineSize * 5)]
private struct CacheLineSeparated
Expand All @@ -61,7 +65,11 @@ private struct CacheLineSeparated
private int _completionCount = 0;
private int _threadAdjustmentIntervalMs;

private LowLevelLock _hillClimbingThreadAdjustmentLock = new LowLevelLock();
#if CORERT
private readonly Lock _hillClimbingThreadAdjustmentLock = new Lock();
#else
private readonly object _hillClimbingThreadAdjustmentLock = new object();
#endif

private volatile int _numRequestedWorkers = 0;

Expand Down Expand Up @@ -90,8 +98,11 @@ private ClrThreadPool()

public bool SetMinThreads(int minThreads)
{
_maxMinThreadLock.Acquire();
try
#if CORERT
using (LockHolder.Hold(_maxMinThreadLock))
#else
lock (_maxMinThreadLock)
#endif
{
if (minThreads < 0 || minThreads > _maxThreads)
{
Expand Down Expand Up @@ -129,18 +140,17 @@ public bool SetMinThreads(int minThreads)
return true;
}
}
finally
{
_maxMinThreadLock.Release();
}
}

public int GetMinThreads() => _minThreads;

public bool SetMaxThreads(int maxThreads)
{
_maxMinThreadLock.Acquire();
try
#if CORERT
using (LockHolder.Hold(_maxMinThreadLock))
#else
lock (_maxMinThreadLock)
#endif
{
if (maxThreads < _minThreads || maxThreads == 0)
{
Expand Down Expand Up @@ -173,10 +183,6 @@ public bool SetMaxThreads(int maxThreads)
return true;
}
}
finally
{
_maxMinThreadLock.Release();
}
}

public int GetMaxThreads() => _maxThreads;
Expand All @@ -197,17 +203,34 @@ internal bool NotifyWorkItemComplete()
// TODO: Check perf. Might need to make this thread-local.
Interlocked.Increment(ref _completionCount);
Volatile.Write(ref _separated.lastDequeueTime, Environment.TickCount);
if (ShouldAdjustMaxWorkersActive() && _hillClimbingThreadAdjustmentLock.TryAcquire())

if (ShouldAdjustMaxWorkersActive())
{
try
#if CORERT
if (_hillClimbingThreadAdjustmentLock.TryAcquire(0))
{
AdjustMaxWorkersActive();
try
{
AdjustMaxWorkersActive();
}
finally
{
_hillClimbingThreadAdjustmentLock.Release();
}
}
finally
#else
if (Monitor.TryEnter(_hillClimbingThreadAdjustmentLock))
{
_hillClimbingThreadAdjustmentLock.Release();
try
{
AdjustMaxWorkersActive();
}
finally
{
Monitor.Exit(_hillClimbingThreadAdjustmentLock);
}
}
#endif
}

return !WorkerThread.ShouldStopProcessingWorkNow();
Expand All @@ -219,7 +242,6 @@ internal bool NotifyWorkItemComplete()
//
private void AdjustMaxWorkersActive()
{
_hillClimbingThreadAdjustmentLock.VerifyIsLocked();
int currentTicks = Environment.TickCount;
int totalNumCompletions = Volatile.Read(ref _completionCount);
int numCompletions = totalNumCompletions - _separated.priorCompletionCount;
Expand Down
Loading