Skip to content

Commit

Permalink
Faster type load for scenarios made more common by generic math (#54588)
Browse files Browse the repository at this point in the history
Change interface map layout in two interesting ways
1. For interface maps defined in System.Private.CoreLib, rely on the C# compiler to prevent any ambiguous duplicates, and to find the full interface expansion, instead of expanding it within the type loader See code marked with #SpecialCorelibInterfaceExpansionAlgorithm
- Note that this optimization is only applied for ValueTypes as the presence of inheritance makes the optimization much more complex in many cases, and isn't needed.
- This optimization reduces the amount of parsing of the interface implementation table must be done for valuetypes in CoreLib. In particular, with the new interfaces that are added as part of #54650 there is a very deep interface hierarchy that requires a great deal of scanning. As those interfaces are added to all the primitive types, the impact on startup performance is significant and measurable.
2. For interface map expansion that follows the curiously recurring generic pattern, place the open instantiation of the type in the interface map instead of the the exact instantiation, and update all places in the runtime which consider the interface map to deal with that change (Mostly by adding special purpose logic to work with the special marker type in the interface map, but there is also logic to simply force the exact interface type to be loaded, when working with the partially loaded type is not quite good enough, or excessively complex)
- This optimization reduces the set of interface types that need to be loaded if they are unused. Of particular benefit are the numerous interfaces associated with the primitive types that are added as part of #54650.

Performance of launching an extremely simple .NET process (process with empty main method). Results acquired using local testing on my developer machine, using a simple script that launches the process 500 times in a row.

| Before #54650 | After #54650 | After #54650 and #54588 (this pr) |
| :-: | :-: | :-: |
| 14.1ms | 16.5ms |14.3ms |
  • Loading branch information
davidwrighton authored Jun 28, 2021
1 parent 3177839 commit 3144188
Show file tree
Hide file tree
Showing 23 changed files with 396 additions and 118 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,8 @@ internal unsafe struct MethodTable
private const uint enum_flag_NonTrivialInterfaceCast = 0x00080000 // enum_flag_Category_Array
| 0x40000000 // enum_flag_ComObject
| 0x00400000 // enum_flag_ICastable;
| 0x00200000;// enum_flag_IDynamicInterfaceCastable;
| 0x00200000 // enum_flag_IDynamicInterfaceCastable;
| 0x00040000; // enum_flag_Category_ValueType

private const int DebugClassNamePtr = // adjust for debug_m_szClassName
#if DEBUG
Expand Down
4 changes: 0 additions & 4 deletions src/coreclr/vm/amd64/asmconstants.h
Original file line number Diff line number Diff line change
Expand Up @@ -203,10 +203,6 @@ ASMCONSTANTS_C_ASSERT(METHODTABLE_EQUIVALENCE_FLAGS
#define METHODTABLE_EQUIVALENCE_FLAGS 0x0
#endif

#define METHODTABLE_NONTRIVIALINTERFACECAST_FLAGS (0x00080000 + 0x40000000 + 0x00400000 + 0x00200000)
ASMCONSTANTS_C_ASSERT(METHODTABLE_NONTRIVIALINTERFACECAST_FLAGS
== MethodTable::enum_flag_NonTrivialInterfaceCast);

#define MethodTable__enum_flag_ContainsPointers 0x01000000
ASMCONSTANTS_C_ASSERT(MethodTable__enum_flag_ContainsPointers
== MethodTable::enum_flag_ContainsPointers);
Expand Down
3 changes: 2 additions & 1 deletion src/coreclr/vm/class.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1903,7 +1903,8 @@ TypeHandle MethodTable::GetDefItfForComClassItf()
InterfaceMapIterator it = IterateInterfaceMap();
if (it.Next())
{
return TypeHandle(it.GetInterface());
// Can use GetInterfaceApprox, as there are no generic default interfaces
return TypeHandle(it.GetInterfaceApprox());
}
else
{
Expand Down
6 changes: 5 additions & 1 deletion src/coreclr/vm/classcompat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1241,7 +1241,11 @@ VOID MethodTableBuilder::BuildInteropVTable_ExpandInterface(InterfaceInfo_t *pIn
if (pNewInterface->GetNumInterfaces() != 0) {
MethodTable::InterfaceMapIterator it = pNewInterface->IterateInterfaceMap();
while (it.Next()) {
BuildInteropVTable_ExpandInterface(pInterfaceMap, it.GetInterface(),
MethodTable *pItf = it.GetInterfaceApprox();
if (pItf->HasInstantiation() || pItf->IsSpecialMarkerTypeForGenericCasting())
continue;

BuildInteropVTable_ExpandInterface(pInterfaceMap, pItf,
pwInterfaceListSize, pdwMaxInterfaceMethods, FALSE);
}
}
Expand Down
7 changes: 4 additions & 3 deletions src/coreclr/vm/clsload.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2281,7 +2281,8 @@ TypeHandle ClassLoader::LoadTypeDefOrRefOrSpecThrowing(Module *pModule,
LoadTypesFlag fLoadTypes/*=LoadTypes*/ ,
ClassLoadLevel level /* = CLASS_LOADED */,
BOOL dropGenericArgumentLevel /* = FALSE */,
const Substitution *pSubst)
const Substitution *pSubst,
MethodTable *pMTInterfaceMapOwner)
{
CONTRACT(TypeHandle)
{
Expand Down Expand Up @@ -2315,7 +2316,7 @@ TypeHandle ClassLoader::LoadTypeDefOrRefOrSpecThrowing(Module *pModule,
}
SigPointer sigptr(pSig, cSig);
TypeHandle typeHnd = sigptr.GetTypeHandleThrowing(pModule, pTypeContext, fLoadTypes,
level, dropGenericArgumentLevel, pSubst);
level, dropGenericArgumentLevel, pSubst, (const ZapSig::Context *)0, pMTInterfaceMapOwner);
#ifndef DACCESS_COMPILE
if ((fNotFoundAction == ThrowIfNotFound) && typeHnd.IsNull())
pModule->GetAssembly()->ThrowTypeLoadException(pInternalImport, typeDefOrRefOrSpec,
Expand Down Expand Up @@ -5294,7 +5295,7 @@ BOOL ClassLoader::CanAccessFamily(
while (it.Next())
{
// We only loosely check if they are of the same generic type
if (it.GetInterface()->HasSameTypeDefAs(pTargetClass))
if (it.HasSameTypeDefAs(pTargetClass))
return TRUE;
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/coreclr/vm/clsload.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -680,7 +680,8 @@ class ClassLoader
LoadTypesFlag fLoadTypes = LoadTypes,
ClassLoadLevel level = CLASS_LOADED,
BOOL dropGenericArgumentLevel = FALSE,
const Substitution *pSubst = NULL /* substitution to apply if the token is a type spec with generic variables */ );
const Substitution *pSubst = NULL /* substitution to apply if the token is a type spec with generic variables */,
MethodTable *pMTInterfaceMapOwner = NULL);

// Load constructed types by providing their constituents
static TypeHandle LoadPointerOrByrefTypeThrowing(CorElementType typ,
Expand Down
8 changes: 5 additions & 3 deletions src/coreclr/vm/comcallablewrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2520,12 +2520,14 @@ static IUnknown * GetComIPFromCCW_HandleExtendsCOMObject(
MethodTable::InterfaceMapIterator intIt = pMT->IterateInterfaceMapFrom(intfIndex);

// If the number of slots is 0, then no need to proceed
if (intIt.GetInterface()->GetNumVirtuals() != 0)
MethodTable* pItf = intIt.GetInterfaceApprox();
if (pItf->GetNumVirtuals() != 0)
{
MethodDesc *pClsMD = NULL;
_ASSERTE(!pItf->HasInstantiation());

// Find the implementation for the first slot of the interface
DispatchSlot impl(pMT->FindDispatchSlot(intIt.GetInterface()->GetTypeID(), 0, FALSE /* throwOnConflict */));
DispatchSlot impl(pMT->FindDispatchSlot(pItf->GetTypeID(), 0, FALSE /* throwOnConflict */));
CONSISTENCY_CHECK(!impl.IsNull());

// Get the MethodDesc for this slot in the class
Expand Down Expand Up @@ -3991,7 +3993,7 @@ ComCallWrapperTemplate::CCWInterfaceMapIterator::CCWInterfaceMapIterator(TypeHan
MethodTable::InterfaceMapIterator it = pMT->IterateInterfaceMap();
while (it.Next())
{
MethodTable *pItfMT = it.GetInterface();
MethodTable *pItfMT = it.GetInterface(pMT);
AppendInterface(pItfMT);
}

Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/vm/cominterfacemarshaler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,7 @@ VOID COMInterfaceMarshaler::EnsureCOMInterfacesSupported(OBJECTREF oref, MethodT

while (it.Next())
{
MethodTable *pItfMT = it.GetInterface();
MethodTable *pItfMT = it.GetInterfaceApprox();
if (!pItfMT)
COMPlusThrow(kInvalidCastException, IDS_EE_CANNOT_COERCE_COMOBJECT);

Expand Down
4 changes: 2 additions & 2 deletions src/coreclr/vm/compile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5235,7 +5235,7 @@ void CEEPreloader::ExpandTypeDependencies(TypeHandle th)
MethodTable::InterfaceMapIterator intIterator = pMT->IterateInterfaceMap();
while (intIterator.Next())
{
TriageTypeForZap(intIterator.GetInterface(), TRUE);
TriageTypeForZap(intIterator.GetInterfaceApprox(), TRUE);
}

// Make sure approx types for all fields are saved
Expand Down Expand Up @@ -5423,7 +5423,7 @@ void CEEPreloader::TriageTypeFromSoftBoundModule(TypeHandle th, Module * pSoftBo
MethodTable::InterfaceMapIterator intIterator = pMT->IterateInterfaceMap();
while (intIterator.Next())
{
TriageTypeFromSoftBoundModule(intIterator.GetInterface(), pSoftBoundModule);
TriageTypeFromSoftBoundModule(intIterator.GetInterfaceApprox(), pSoftBoundModule);
}

// It does not seem worth it to reject the remaining items
Expand Down
6 changes: 5 additions & 1 deletion src/coreclr/vm/generics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -705,7 +705,11 @@ BOOL RecursionGraph::CheckForIllegalRecursion()
MethodTable::InterfaceMapIterator it = pMT->IterateInterfaceMap();
while (it.Next())
{
AddDependency(it.GetInterface());
MethodTable *pItfApprox = it.GetInterfaceApprox();
if (!pItfApprox->IsTypicalTypeDefinition())
{
AddDependency(pItfApprox);
}
}

// Check all owned nodes for expanding cycles. The edges recorded above must all
Expand Down
7 changes: 4 additions & 3 deletions src/coreclr/vm/jitinterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8973,7 +8973,7 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info)
int canonicallyMatchingInterfacesFound = 0;
while (it.Next())
{
if (it.GetInterface()->GetCanonicalMethodTable() == pOwnerMT)
if (it.GetInterface(pObjMT)->GetCanonicalMethodTable() == pOwnerMT)
{
canonicallyMatchingInterfacesFound++;
if (canonicallyMatchingInterfacesFound > 1)
Expand Down Expand Up @@ -14301,9 +14301,10 @@ BOOL LoadDynamicInfoEntry(Module *currentModule,
MethodTable::InterfaceMapIterator it = thImpl.GetMethodTable()->IterateInterfaceMap();
while (it.Next())
{
if (pInterfaceTypeCanonical == it.GetInterface()->GetCanonicalMethodTable())
MethodTable *pItfInMap = it.GetInterface(thImpl.GetMethodTable());
if (pInterfaceTypeCanonical == pItfInMap->GetCanonicalMethodTable())
{
pDeclMethod = MethodDesc::FindOrCreateAssociatedMethodDesc(pDeclMethod, it.GetInterface(), FALSE, pDeclMethod->GetMethodInstantiation(), FALSE, TRUE);
pDeclMethod = MethodDesc::FindOrCreateAssociatedMethodDesc(pDeclMethod, pItfInMap, FALSE, pDeclMethod->GetMethodInstantiation(), FALSE, TRUE);
break;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/vm/marshalnative.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1064,7 +1064,7 @@ FCIMPL2(Object*, MarshalNative::InternalCreateWrapperOfType, Object* objUNSAFE,
MethodTable::InterfaceMapIterator it = pNewWrapMT->IterateInterfaceMap();
while (it.Next())
{
MethodTable *pItfMT = it.GetInterface();
MethodTable *pItfMT = it.GetInterfaceApprox(); // ComImport interfaces cannot be generic
if (pItfMT->IsComImport())
{
if (!Object::SupportsInterface(gc.obj, pItfMT))
Expand Down
Loading

0 comments on commit 3144188

Please sign in to comment.