Skip to content

Commit 9a3cacd

Browse files
Move PendingTypeLoadTable from the ClassLoader object to a global structure (#96653)
- Shard the hashtable so that it will rarely have lock contention - Pre-allocate the PendingTypeLoad Entries. This reduces allocator pressure on startup substantially, especially in the presence of multithreaded loading where the struct is allocated on 1 thread and often freed on another. The effectiveness of the pre-allocation and sharding heuristics was measured on a complex ASP.NET scenario tweaked to perform extremely high numbers of multithreaded loads and produced startup wins of about 10%.
1 parent 4644486 commit 9a3cacd

14 files changed

+457
-449
lines changed

src/coreclr/vm/ceemain.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@
162162
#include "disassembler.h"
163163
#include "jithost.h"
164164
#include "pgo.h"
165+
#include "pendingload.h"
165166

166167
#ifndef TARGET_UNIX
167168
#include "dwreport.h"
@@ -623,6 +624,8 @@ void EEStartupHelper()
623624
// This needs to be done before the EE has started
624625
InitializeStartupFlags();
625626

627+
PendingTypeLoadTable::Init();
628+
626629
IfFailGo(ExecutableAllocator::StaticInitialize(FatalErrorHandler));
627630

628631
Thread::StaticInitialize();

src/coreclr/vm/clsload.cpp

Lines changed: 27 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ Module * ClassLoader::ComputeLoaderModule(MethodTable * pMT,
216216
methodInst);
217217
}
218218
/*static*/
219-
Module *ClassLoader::ComputeLoaderModule(TypeKey *typeKey)
219+
Module *ClassLoader::ComputeLoaderModule(const TypeKey *typeKey)
220220
{
221221
CONTRACTL
222222
{
@@ -729,7 +729,7 @@ void ClassLoader::LazyPopulateCaseInsensitiveHashTables()
729729
}
730730

731731
/*static*/
732-
void DECLSPEC_NORETURN ClassLoader::ThrowTypeLoadException(TypeKey *pKey,
732+
void DECLSPEC_NORETURN ClassLoader::ThrowTypeLoadException(const TypeKey *pKey,
733733
UINT resIDWhy)
734734
{
735735
STATIC_CONTRACT_THROWS;
@@ -743,7 +743,7 @@ void DECLSPEC_NORETURN ClassLoader::ThrowTypeLoadException(TypeKey *pKey,
743743

744744
#endif
745745

746-
TypeHandle ClassLoader::LoadConstructedTypeThrowing(TypeKey *pKey,
746+
TypeHandle ClassLoader::LoadConstructedTypeThrowing(const TypeKey *pKey,
747747
LoadTypesFlag fLoadTypes /*= LoadTypes*/,
748748
ClassLoadLevel level /*=CLASS_LOADED*/,
749749
const InstantiationContext *pInstContext /*=NULL*/)
@@ -858,7 +858,7 @@ void ClassLoader::TryEnsureLoaded(TypeHandle typeHnd, ClassLoadLevel level)
858858
}
859859

860860
/* static */
861-
TypeHandle ClassLoader::LookupTypeKey(TypeKey *pKey, EETypeHashTable *pTable)
861+
TypeHandle ClassLoader::LookupTypeKey(const TypeKey *pKey, EETypeHashTable *pTable)
862862
{
863863
CONTRACTL {
864864
NOTHROW;
@@ -875,7 +875,7 @@ TypeHandle ClassLoader::LookupTypeKey(TypeKey *pKey, EETypeHashTable *pTable)
875875
}
876876

877877
/* static */
878-
TypeHandle ClassLoader::LookupInLoaderModule(TypeKey *pKey)
878+
TypeHandle ClassLoader::LookupInLoaderModule(const TypeKey *pKey)
879879
{
880880
CONTRACTL {
881881
NOTHROW;
@@ -895,7 +895,7 @@ TypeHandle ClassLoader::LookupInLoaderModule(TypeKey *pKey)
895895

896896

897897
/* static */
898-
TypeHandle ClassLoader::LookupTypeHandleForTypeKey(TypeKey *pKey)
898+
TypeHandle ClassLoader::LookupTypeHandleForTypeKey(const TypeKey *pKey)
899899
{
900900
CONTRACTL
901901
{
@@ -1659,12 +1659,6 @@ ClassLoader::~ClassLoader()
16591659
}
16601660
CONTRACTL_END
16611661

1662-
#ifdef _DEBUG
1663-
// Do not walk m_pUnresolvedClassHash at destruct time as it is loaderheap allocated memory
1664-
// and may already have been deallocated via an AllocMemTracker.
1665-
m_pUnresolvedClassHash = (PendingTypeLoadTable*)(UINT_PTR)0xcccccccc;
1666-
#endif
1667-
16681662
#ifdef _DEBUG
16691663
// LOG((
16701664
// LF_CLASSLOADER,
@@ -1704,7 +1698,6 @@ ClassLoader::~ClassLoader()
17041698

17051699
FreeModules();
17061700

1707-
m_UnresolvedClassLock.Destroy();
17081701
m_AvailableClassLock.Destroy();
17091702
m_AvailableTypesLock.Destroy();
17101703
}
@@ -1729,7 +1722,6 @@ ClassLoader::ClassLoader(Assembly *pAssembly)
17291722

17301723
m_pAssembly = pAssembly;
17311724

1732-
m_pUnresolvedClassHash = NULL;
17331725
m_cUnhashedModules = 0;
17341726

17351727
#ifdef _DEBUG
@@ -1760,12 +1752,6 @@ VOID ClassLoader::Init(AllocMemTracker *pamTracker)
17601752
{
17611753
STANDARD_VM_CONTRACT;
17621754

1763-
m_pUnresolvedClassHash = PendingTypeLoadTable::Create(GetAssembly()->GetLowFrequencyHeap(),
1764-
UNRESOLVED_CLASS_HASH_BUCKETS,
1765-
pamTracker);
1766-
1767-
m_UnresolvedClassLock.Init(CrstUnresolvedClassLock);
1768-
17691755
// This lock is taken within the classloader whenever we have to enter a
17701756
// type in one of the modules governed by the loader.
17711757
// The process of creating these types may be reentrant. The ordering has
@@ -2605,7 +2591,7 @@ ClassLoader::LoadApproxParentThrowing(
26052591
// Perform a single phase of class loading
26062592
// It is the caller's responsibility to lock
26072593
/*static*/
2608-
TypeHandle ClassLoader::DoIncrementalLoad(TypeKey *pTypeKey, TypeHandle typeHnd, ClassLoadLevel currentLevel)
2594+
TypeHandle ClassLoader::DoIncrementalLoad(const TypeKey *pTypeKey, TypeHandle typeHnd, ClassLoadLevel currentLevel)
26092595
{
26102596
CONTRACTL
26112597
{
@@ -2679,7 +2665,7 @@ TypeHandle ClassLoader::DoIncrementalLoad(TypeKey *pTypeKey, TypeHandle typeHnd,
26792665
// For canonical instantiations of generic types, create a brand new method table
26802666
// For other constructed types, create a type desc and template method table if necessary
26812667
// For all other types, create a method table
2682-
TypeHandle ClassLoader::CreateTypeHandleForTypeKey(TypeKey* pKey, AllocMemTracker* pamTracker)
2668+
TypeHandle ClassLoader::CreateTypeHandleForTypeKey(const TypeKey* pKey, AllocMemTracker* pamTracker)
26832669
{
26842670
CONTRACT(TypeHandle)
26852671
{
@@ -2794,7 +2780,7 @@ TypeHandle ClassLoader::CreateTypeHandleForTypeKey(TypeKey* pKey, AllocMemTracke
27942780
// particular, exact parent info (base class and interfaces) is loaded
27952781
// in a later phase
27962782
/*static*/
2797-
TypeHandle ClassLoader::PublishType(TypeKey *pTypeKey, TypeHandle typeHnd)
2783+
TypeHandle ClassLoader::PublishType(const TypeKey *pTypeKey, TypeHandle typeHnd)
27982784
{
27992785
CONTRACTL
28002786
{
@@ -2994,7 +2980,7 @@ static void PushFinalLevels(TypeHandle typeHnd, ClassLoadLevel targetLevel, cons
29942980

29952981

29962982
//
2997-
TypeHandle ClassLoader::LoadTypeHandleForTypeKey(TypeKey *pTypeKey,
2983+
TypeHandle ClassLoader::LoadTypeHandleForTypeKey(const TypeKey *pTypeKey,
29982984
TypeHandle typeHnd,
29992985
ClassLoadLevel targetLevel/*=CLASS_LOADED*/,
30002986
const InstantiationContext *pInstContext/*=NULL*/)
@@ -3018,8 +3004,7 @@ TypeHandle ClassLoader::LoadTypeHandleForTypeKey(TypeKey *pTypeKey,
30183004
SString name;
30193005
TypeString::AppendTypeKeyDebug(name, pTypeKey);
30203006
LOG((LF_CLASSLOADER, LL_INFO10000, "PHASEDLOAD: LoadTypeHandleForTypeKey for type %s to level %s\n", name.GetUTF8(), classLoadLevelName[targetLevel]));
3021-
CrstHolder unresolvedClassLockHolder(&m_UnresolvedClassLock);
3022-
m_pUnresolvedClassHash->Dump();
3007+
PendingTypeLoadTable::GetTable()->Dump();
30233008
}
30243009
#endif
30253010

@@ -3051,7 +3036,7 @@ TypeHandle ClassLoader::LoadTypeHandleForTypeKey(TypeKey *pTypeKey,
30513036
}
30523037

30533038
//
3054-
TypeHandle ClassLoader::LoadTypeHandleForTypeKeyNoLock(TypeKey *pTypeKey,
3039+
TypeHandle ClassLoader::LoadTypeHandleForTypeKeyNoLock(const TypeKey *pTypeKey,
30553040
ClassLoadLevel targetLevel/*=CLASS_LOADED*/,
30563041
const InstantiationContext *pInstContext/*=NULL*/)
30573042
{
@@ -3091,11 +3076,11 @@ TypeHandle ClassLoader::LoadTypeHandleForTypeKeyNoLock(TypeKey *pTypeKey,
30913076
class PendingTypeLoadHolder
30923077
{
30933078
Thread * m_pThread;
3094-
PendingTypeLoadEntry * m_pEntry;
3079+
PendingTypeLoadTable::Entry * m_pEntry;
30953080
PendingTypeLoadHolder * m_pPrevious;
30963081

30973082
public:
3098-
PendingTypeLoadHolder(PendingTypeLoadEntry * pEntry)
3083+
PendingTypeLoadHolder(PendingTypeLoadTable::Entry * pEntry)
30993084
{
31003085
LIMITED_METHOD_CONTRACT;
31013086

@@ -3114,7 +3099,7 @@ class PendingTypeLoadHolder
31143099
m_pThread->SetPendingTypeLoad(m_pPrevious);
31153100
}
31163101

3117-
static bool CheckForDeadLockOnCurrentThread(PendingTypeLoadEntry * pEntry)
3102+
static bool CheckForDeadLockOnCurrentThread(PendingTypeLoadTable::Entry * pEntry)
31183103
{
31193104
LIMITED_METHOD_CONTRACT;
31203105

@@ -3136,7 +3121,7 @@ class PendingTypeLoadHolder
31363121
//
31373122
TypeHandle
31383123
ClassLoader::LoadTypeHandleForTypeKey_Body(
3139-
TypeKey * pTypeKey,
3124+
const TypeKey * pTypeKey,
31403125
TypeHandle typeHnd,
31413126
ClassLoadLevel targetLevel)
31423127
{
@@ -3167,14 +3152,16 @@ ClassLoader::LoadTypeHandleForTypeKey_Body(
31673152
#endif
31683153
}
31693154

3170-
ReleaseHolder<PendingTypeLoadEntry> pLoadingEntry;
3171-
CrstHolderWithState unresolvedClassLockHolder(&m_UnresolvedClassLock, false);
3155+
ReleaseHolder<PendingTypeLoadTable::Entry> pLoadingEntry;
3156+
DWORD dwHashedTypeKey;
3157+
PendingTypeLoadTable::Shard *pPendingTypeLoadShard = PendingTypeLoadTable::GetTable()->GetShard(*pTypeKey, this, &dwHashedTypeKey);
3158+
CrstHolderWithState unresolvedClassLockHolder(pPendingTypeLoadShard->GetCrst(), false);
31723159

31733160
retry:
31743161
unresolvedClassLockHolder.Acquire();
31753162

31763163
// Is it in the hash of classes currently being loaded?
3177-
pLoadingEntry = m_pUnresolvedClassHash->GetValue(pTypeKey);
3164+
pLoadingEntry = pPendingTypeLoadShard->FindPendingTypeLoadEntry(dwHashedTypeKey, *pTypeKey);
31783165
if (pLoadingEntry)
31793166
{
31803167
pLoadingEntry->AddRef();
@@ -3219,15 +3206,8 @@ ClassLoader::LoadTypeHandleForTypeKey_Body(
32193206
goto retry;
32203207
}
32213208

3222-
{
3223-
// Wait for class to be loaded by another thread. This is where we start tracking the
3224-
// entry, so there is an implicit Acquire in our use of Assign here.
3225-
CrstHolder loadingEntryLockHolder(&pLoadingEntry->m_Crst);
3226-
_ASSERTE(pLoadingEntry->HasLock());
3227-
}
3228-
32293209
// Result of other thread loading the class
3230-
HRESULT hr = pLoadingEntry->m_hrResult;
3210+
HRESULT hr = pLoadingEntry->DelayForProgress(&typeHnd);
32313211

32323212
if (FAILED(hr)) {
32333213

@@ -3262,9 +3242,6 @@ ClassLoader::LoadTypeHandleForTypeKey_Body(
32623242
pLoadingEntry->ThrowException();
32633243
}
32643244

3265-
// Get a pointer to the EEClass being loaded
3266-
typeHnd = pLoadingEntry->m_typeHandle;
3267-
32683245
if (!typeHnd.IsNull())
32693246
{
32703247
// If the type load on the other thread loaded the type to the needed level, return it here.
@@ -3294,12 +3271,7 @@ ClassLoader::LoadTypeHandleForTypeKey_Body(
32943271

32953272
// It was not loaded, and it is not being loaded, so we must load it. Create a new LoadingEntry
32963273
// and acquire it immediately so that other threads will block.
3297-
pLoadingEntry = new PendingTypeLoadEntry(*pTypeKey, typeHnd); // this atomically creates a crst and acquires it
3298-
3299-
if (!(m_pUnresolvedClassHash->InsertValue(pLoadingEntry)))
3300-
{
3301-
COMPlusThrowOM();
3302-
}
3274+
pLoadingEntry = pPendingTypeLoadShard->InsertPendingTypeLoadEntry(dwHashedTypeKey, *pTypeKey, typeHnd); // this atomically creates a crst and acquires it
33033275

33043276
// Leave the global lock, so that other threads may now start waiting on our class's lock
33053277
unresolvedClassLockHolder.Release();
@@ -3337,7 +3309,7 @@ ClassLoader::LoadTypeHandleForTypeKey_Body(
33373309

33383310
// Unlink this class from the unresolved class list.
33393311
unresolvedClassLockHolder.Acquire();
3340-
m_pUnresolvedClassHash->DeleteValue(pTypeKey);
3312+
pPendingTypeLoadShard->RemovePendingTypeLoadEntry(pLoadingEntry);
33413313

33423314
// Release the lock before proceeding. The unhandled exception filters take number of locks that
33433315
// have ordering violations with this lock.
@@ -3350,13 +3322,13 @@ ClassLoader::LoadTypeHandleForTypeKey_Body(
33503322

33513323
// Unlink this class from the unresolved class list.
33523324
unresolvedClassLockHolder.Acquire();
3353-
m_pUnresolvedClassHash->DeleteValue(pTypeKey);
3325+
pPendingTypeLoadShard->RemovePendingTypeLoadEntry(pLoadingEntry);
33543326
unresolvedClassLockHolder.Release();
33553327

33563328
// Unblock any thread waiting to load same type as in TypeLoadEntry. This should be done
3357-
// after pLoadingEntry is removed from m_pUnresolvedClassHash. Otherwise the other thread
3329+
// after pLoadingEntry is removed from the PendingTypeLoadTable. Otherwise the other thread
33583330
// (which was waiting) will keep spinning for a while after waking up, till the current thread removes
3359-
// pLoadingEntry from m_pUnresolvedClassHash. This can cause hang in situation when the current
3331+
// pLoadingEntry from the PendingTypeLoadTable. This can cause hang in situation when the current
33603332
// thread is a background thread and so will get very less processor cycle to perform subsequent
33613333
// operations to remove the entry from hash later.
33623334
pLoadingEntry->UnblockWaiters();

0 commit comments

Comments
 (0)