Skip to content

Commit

Permalink
Refactor call counting for tiering and some other things (dotnet#26393)
Browse files Browse the repository at this point in the history
Refactor call counting for tiering and some other things

- Moved call counting to after jitting is done, when the tier of the code becomes clear. Passed info back about whether the method should be recorded for call counting through the `PrepareCodeConfig` to avoid some more expensive rechecks.
- Moved checks for aggressive optimization and QuickJit to the point when JIT flags are determined
- Specialized `PublishVersionalbeCodeIfNecessary` for methods versionable with and without a jump stamp
- Added fast paths for cases operating on the default native code version to avoid code versioning overhead during startup
- Methods are not counted on first call, only recorded for counting later. First call to a method is determined by whether the thread generated or loaded code for the method. Threads that wait for jitting to complete don't count calls either. Once the method already has native code and goes through the prestub, its calls are counted.
- Overall, time spent in DoPrestub decreased noticeably when tiering is enabled, almost the same as with tiering disabled typically. Small improvement to startup time, seems to decrease by a few milliseconds.
- Fixed a bug where, when call counting for a method stops due to the tiering delay being activated, it was extending the delay as though it were the first call to the method
  - This was noticably increasing time taken to reach steady-state perf by keeping the delay active for longer than necessary. After the fix, steady-state perf is reached sooner without any noticeable overhead in-between.
  - A fix creates a potential race between thread A recording a method to be counted later, thread B resetting the method's entry point for counting calls, then thread A setting the tier 0 code as the entry point, and the method would not be tiered up. For now I decided to stop call counting only when a flag that would extend the delay timer is set, with other planned work this code and issue would go away.
- Miscellaneous small changes
  • Loading branch information
kouvel committed Sep 3, 2019
1 parent f82281f commit 6e72575
Show file tree
Hide file tree
Showing 10 changed files with 732 additions and 348 deletions.
4 changes: 2 additions & 2 deletions src/inc/clrtypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@
#include "static_assert.h"

#if BIT64
#define POINTER_BITS 64
#define POINTER_BITS (64)
#else
#define POINTER_BITS 32
#define POINTER_BITS (32)
#endif

// ================================================================================
Expand Down
77 changes: 38 additions & 39 deletions src/vm/callcounter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,21 +36,11 @@ CallCounter::CallCounter()

#endif // !DACCESS_COMPILE

bool CallCounter::IsEligibleForCallCounting(PTR_MethodDesc pMethodDesc)
{
WRAPPER_NO_CONTRACT;
_ASSERTE(pMethodDesc != NULL);
_ASSERTE(pMethodDesc->IsEligibleForTieredCompilation());

return g_pConfig->TieredCompilation_CallCounting() && !pMethodDesc->RequestedAggressiveOptimization();
}

bool CallCounter::IsCallCountingEnabled(PTR_MethodDesc pMethodDesc)
{
WRAPPER_NO_CONTRACT;
_ASSERTE(pMethodDesc != PTR_NULL);
_ASSERTE(pMethodDesc->IsEligibleForTieredCompilation());
_ASSERTE(IsEligibleForCallCounting(pMethodDesc));

#ifndef DACCESS_COMPILE
SpinLockHolder holder(&m_lock);
Expand All @@ -68,7 +58,6 @@ void CallCounter::DisableCallCounting(MethodDesc* pMethodDesc)
WRAPPER_NO_CONTRACT;
_ASSERTE(pMethodDesc != NULL);
_ASSERTE(pMethodDesc->IsEligibleForTieredCompilation());
_ASSERTE(IsEligibleForCallCounting(pMethodDesc));

// Disabling call counting will affect the tier of the MethodDesc's first native code version. Callers must ensure that this
// change is made deterministically and prior to or while jitting the first native code version such that the tier would not
Expand All @@ -91,38 +80,49 @@ void CallCounter::DisableCallCounting(MethodDesc* pMethodDesc)
m_methodToCallCount.Add(CallCounterEntry::CreateWithCallCountingDisabled(pMethodDesc));
}

bool CallCounter::WasCalledAtMostOnce(MethodDesc* pMethodDesc)
NOINLINE bool CallCounter::OnMethodCodeVersionCalledSubsequently(NativeCodeVersion nativeCodeVersion, bool *doPublishRef)
{
WRAPPER_NO_CONTRACT;
STANDARD_VM_CONTRACT;
_ASSERTE(!nativeCodeVersion.IsNull());
_ASSERTE(nativeCodeVersion.GetNativeCode() != NULL);
_ASSERTE(doPublishRef != nullptr);
_ASSERTE(*doPublishRef);

MethodDesc *methodDesc = nativeCodeVersion.GetMethodDesc();
if (!methodDesc->IsEligibleForTieredCompilation() ||
nativeCodeVersion.GetOptimizationTier() != NativeCodeVersion::OptimizationTier0)
{
return false;
}

SpinLockHolder holder(&m_lock);
TieredCompilationManager *tieredCompilationManager = GetAppDomain()->GetTieredCompilationManager();
if (tieredCompilationManager->OnMethodCodeVersionCalledSubsequently(methodDesc))
{
return true;
}

const CallCounterEntry *existingEntry = m_methodToCallCount.LookupPtr(pMethodDesc);
return
existingEntry == nullptr ||
existingEntry->callCountLimit >= (int)g_pConfig->TieredCompilation_CallCountThreshold() - 1;
if (methodDesc->GetCallCounter()->IncrementCount(methodDesc))
{
*doPublishRef = false;
}
return true;
}

// This is called by the prestub each time the method is invoked in a particular
// AppDomain (the AppDomain for which AppDomain.GetCallCounter() == this). These
// calls continue until we backpatch the prestub to avoid future calls. This allows
// us to track the number of calls to each method and use it as a trigger for tiered
// compilation.
void CallCounter::OnMethodCalled(
MethodDesc* pMethodDesc,
TieredCompilationManager *pTieredCompilationManager,
BOOL* shouldStopCountingCallsRef,
BOOL* wasPromotedToNextTierRef)
bool CallCounter::IncrementCount(MethodDesc* pMethodDesc)
{
STANDARD_VM_CONTRACT;

_ASSERTE(pMethodDesc->IsEligibleForTieredCompilation());
_ASSERTE(pTieredCompilationManager != nullptr);
_ASSERTE(shouldStopCountingCallsRef != nullptr);
_ASSERTE(wasPromotedToNextTierRef != nullptr);

// At the moment, call counting is only done for tier 0 code
_ASSERTE(IsEligibleForCallCounting(pMethodDesc));
if (!g_pConfig->TieredCompilation_CallCounting())
{
return false; // stop counting calls
}

// PERF: This as a simple to implement, but not so performant, call counter
// Currently this is only called until we reach a fixed call count and then
Expand All @@ -137,7 +137,6 @@ void CallCounter::OnMethodCalled(
// leaving the prestub unpatched, but may not be good overall as it increases
// the size of the jitted code.

bool isFirstCall = false;
int callCountLimit;
{
//Be careful if you convert to something fully lock/interlocked-free that
Expand All @@ -150,7 +149,6 @@ void CallCounter::OnMethodCalled(
CallCounterEntry* pEntry = const_cast<CallCounterEntry*>(m_methodToCallCount.LookupPtr(pMethodDesc));
if (pEntry == NULL)
{
isFirstCall = true;
callCountLimit = (int)g_pConfig->TieredCompilation_CallCountThreshold() - 1;
_ASSERTE(callCountLimit >= 0);
m_methodToCallCount.Add(CallCounterEntry(pMethodDesc, callCountLimit));
Expand All @@ -161,18 +159,19 @@ void CallCounter::OnMethodCalled(
}
else
{
*shouldStopCountingCallsRef = true;
*wasPromotedToNextTierRef = true;
return;
return false; // stop counting calls
}
}

pTieredCompilationManager->OnMethodCalled(
pMethodDesc,
isFirstCall,
callCountLimit,
shouldStopCountingCallsRef,
wasPromotedToNextTierRef);
if (callCountLimit > 0)
{
return true; // continue counting calls
}
if (callCountLimit == 0)
{
GetAppDomain()->GetTieredCompilationManager()->AsyncPromoteMethodToTier1(pMethodDesc);
}
return false; // stop counting calls
}

#endif // !DACCESS_COMPILE
Expand Down
5 changes: 2 additions & 3 deletions src/vm/callcounter.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,13 @@ class CallCounter
CallCounter();
#endif

static bool IsEligibleForCallCounting(PTR_MethodDesc pMethodDesc);
bool IsCallCountingEnabled(PTR_MethodDesc pMethodDesc);
#ifndef DACCESS_COMPILE
void DisableCallCounting(MethodDesc* pMethodDesc);
bool WasCalledAtMostOnce(MethodDesc* pMethodDesc);
#endif

void OnMethodCalled(MethodDesc* pMethodDesc, TieredCompilationManager *pTieredCompilationManager, BOOL* shouldStopCountingCallsRef, BOOL* wasPromotedToNextTierRef);
static bool OnMethodCodeVersionCalledSubsequently(NativeCodeVersion nativeCodeVersion, bool *doPublishRef);
bool IncrementCount(MethodDesc* pMethodDesc);

private:

Expand Down
Loading

0 comments on commit 6e72575

Please sign in to comment.