Skip to content
This repository has been archived by the owner on Nov 1, 2020. It is now read-only.

Commit

Permalink
Speed up typeof(Foo) and Object.GetType by 50+% (#8095)
Browse files Browse the repository at this point in the history
This adds a general purpose concept of "writable data" to the EEType data structure. This points to a block of memory whose purpose is defined by the class library (we can potentially cache all sorts of stuff there).

The writable data is used to hold a GC handle to the runtime `System.Type` instance associated with the EEType. This bypasses the hashtable lookup that has been used before.

I'm seeing more than 50% TP improvement against best case scenarios with the hashtable (best case defined as "the thing we're looking for is the first entry in the hashtable bucket").

Both CoreCLR and Mono use a similar approach. CoreRT was lagging in perf because of the hashtable lookup: this brings us much closer. We're still not as fast as CoreCLR, but getting faster than this would have impacts elsewhere (in terms of working set and size on disk). The implementation I chose for CoreRT (optional relative pointer field) has minimal working set impact.
  • Loading branch information
MichalStrehovsky committed Apr 20, 2020
1 parent 191a3c1 commit ec03519
Show file tree
Hide file tree
Showing 17 changed files with 244 additions and 48 deletions.
2 changes: 1 addition & 1 deletion dependencies.props
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<RyuJITVersion Condition="'$(RyuJITVersion)' == ''">5.0.0-preview.4.20205.13</RyuJITVersion>
<ObjectWriterVersion Condition="'$(ObjectWriterVersion)' == ''">1.0.0-alpha-28204-03</ObjectWriterVersion>
<ObjectWriterVersion Condition="'$(ObjectWriterVersion)' == ''">1.0.0-alpha-28820-01</ObjectWriterVersion>
<RuntimeLibrariesVersion Condition="'$(RuntimeLibrariesVersion)' == ''">5.0.0-preview.2.20153.3</RuntimeLibrariesVersion>
<CoreFxUapVersion Condition="'$(CoreFxUapVersion)' == ''">4.7.0-preview6.19265.2</CoreFxUapVersion>
<MicrosoftNETCoreAppPackageVersion Condition="'$(MicrosoftNETCoreAppPackageVersion)' == ''">2.1.14</MicrosoftNETCoreAppPackageVersion>
Expand Down
7 changes: 7 additions & 0 deletions src/Common/src/Internal/Runtime/EEType.Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ internal enum EETypeField
{
ETF_InterfaceMap,
ETF_TypeManagerIndirection,
ETF_WritableData,
ETF_Finalizer,
ETF_OptionalFieldsPtr,
ETF_SealedVirtualSlots,
Expand Down Expand Up @@ -296,4 +297,10 @@ internal static class StringComponentSize
{
public const int Value = sizeof(char);
}

internal static class WritableData
{
public static int GetSize(int pointerSize) => pointerSize;
public static int GetAlignment(int pointerSize) => pointerSize;
}
}
60 changes: 58 additions & 2 deletions src/Common/src/Internal/Runtime/EEType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,18 @@ internal static bool SupportsRelativePointers
}
}

/// <summary>
/// Gets a value indicating whether writable data is supported.
/// </summary>
internal static bool SupportsWritableData
{
get
{
// For now just key this off of SupportsRelativePointer to avoid this on both CppCodegen and WASM.
return SupportsRelativePointers;
}
}

private ushort _usComponentSize;
private ushort _usFlags;
private uint _uBaseSize;
Expand Down Expand Up @@ -1248,6 +1260,35 @@ internal IntPtr PointerToTypeManager
}
#endif

/// <summary>
/// Gets a pointer to a segment of writable memory associated with this EEType.
/// The purpose of the segment is controlled by the class library. The runtime doesn't
/// use this memory for any purpose.
/// </summary>
internal IntPtr WritableData
{
get
{
Debug.Assert(SupportsWritableData);

uint offset = GetFieldOffset(EETypeField.ETF_WritableData);

if (!IsDynamicType)
return GetField<RelativePointer>(offset).Value;
else
return GetField<Pointer>(offset).Value;
}
#if TYPE_LOADER_IMPLEMENTATION
set
{
Debug.Assert(IsDynamicType && SupportsWritableData);

uint cbOffset = GetFieldOffset(EETypeField.ETF_WritableData);
*(IntPtr*)((byte*)Unsafe.AsPointer(ref this) + cbOffset) = value;
}
#endif
}

internal unsafe EETypeRareFlags RareFlags
{
get
Expand Down Expand Up @@ -1319,6 +1360,16 @@ public uint GetFieldOffset(EETypeField eField)
}
cbOffset += relativeOrFullPointerOffset;

// Followed by writable data.
if (SupportsWritableData)
{
if (eField == EETypeField.ETF_WritableData)
{
return cbOffset;
}
cbOffset += relativeOrFullPointerOffset;
}

// Followed by the pointer to the finalizer method.
if (eField == EETypeField.ETF_Finalizer)
{
Expand Down Expand Up @@ -1421,8 +1472,12 @@ public uint GetFieldOffset(EETypeField eField)

public ref T GetField<T>(EETypeField eField)
{
fixed (EEType* pThis = &this)
return ref Unsafe.AddByteOffset(ref Unsafe.As<EEType, T>(ref *pThis), (IntPtr)GetFieldOffset(eField));
return ref Unsafe.As<byte, T>(ref *((byte*)Unsafe.AsPointer(ref this) + GetFieldOffset(eField)));
}

public ref T GetField<T>(uint offset)
{
return ref Unsafe.As<byte, T>(ref *((byte*)Unsafe.AsPointer(ref this) + offset));
}

#if TYPE_LOADER_IMPLEMENTATION
Expand All @@ -1441,6 +1496,7 @@ internal static UInt32 GetSizeofEEType(
(IntPtr.Size * cVirtuals) +
(sizeof(EEInterfaceInfo) * cInterfaces) +
sizeof(IntPtr) + // TypeManager
(SupportsWritableData ? sizeof(IntPtr) : 0) + // WritableData
(fHasFinalizer ? sizeof(UIntPtr) : 0) +
(fRequiresOptionalFields ? sizeof(IntPtr) : 0) +
(fHasSealedVirtuals ? sizeof(IntPtr) : 0) +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ namespace ILCompiler.DependencyAnalysis
/// |
/// [Relative ptr] | Pointer to containing TypeManager indirection cell
/// |
/// [Relative ptr] | Pointer to writable data
/// |
/// [Relative ptr] | Pointer to finalizer method (optional)
/// |
/// [Relative ptr] | Pointer to optional fields (optional)
Expand Down Expand Up @@ -491,6 +493,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly)
}

OutputTypeManagerIndirection(factory, ref objData);
OutputWritableData(factory, ref objData);
OutputFinalizerMethod(factory, ref objData);
OutputOptionalFields(factory, ref objData);
OutputSealedVTable(factory, relocsOnly, ref objData);
Expand Down Expand Up @@ -825,6 +828,21 @@ protected void OutputTypeManagerIndirection(NodeFactory factory, ref ObjectDataB
objData.EmitPointerReloc(factory.TypeManagerIndirection);
}

protected void OutputWritableData(NodeFactory factory, ref ObjectDataBuilder objData)
{
if (factory.Target.SupportsRelativePointers)
{
Utf8StringBuilder writableDataBlobName = new Utf8StringBuilder();
writableDataBlobName.Append("__writableData");
writableDataBlobName.Append(factory.NameMangler.GetMangledTypeName(_type));

BlobNode blob = factory.UninitializedWritableDataBlob(writableDataBlobName.ToUtf8String(),
WritableData.GetSize(factory.Target.PointerSize), WritableData.GetAlignment(factory.Target.PointerSize));

objData.EmitReloc(blob, RelocType.IMAGE_REL_BASED_RELPTR32);
}
}

protected void OutputOptionalFields(NodeFactory factory, ref ObjectDataBuilder objData)
{
if (HasOptionalFields)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
dataBuilder.EmitShort(0); // No interface map
dataBuilder.EmitInt(_type.GetHashCode());
OutputTypeManagerIndirection(factory, ref dataBuilder);
OutputWritableData(factory, ref dataBuilder);
OutputOptionalFields(factory, ref dataBuilder);

return dataBuilder.ToObjectData();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,11 @@ private void CreateNodeCaches()
return new BlobNode(key.Name, ObjectNodeSection.ReadOnlyDataSection, key.Data, key.Alignment);
});

_uninitializedWritableDataBlobs = new NodeCache<UninitializedWritableDataBlobKey, BlobNode>(key =>
{
return new BlobNode(key.Name, ObjectNodeSection.BssSection, new byte[key.Size], key.Alignment);
});

_settableReadOnlyDataBlobs = new NodeCache<Utf8String, SettableReadOnlyDataBlob>(key =>
{
return new SettableReadOnlyDataBlob(key, ObjectNodeSection.ReadOnlyDataSection);
Expand Down Expand Up @@ -655,6 +660,13 @@ public ISymbolNode GCStaticEEType(GCPointerMap gcMap)
return _GCStaticEETypes.GetOrAdd(gcMap);
}

private NodeCache<UninitializedWritableDataBlobKey, BlobNode> _uninitializedWritableDataBlobs;

public BlobNode UninitializedWritableDataBlob(Utf8String name, int size, int alignment)
{
return _uninitializedWritableDataBlobs.GetOrAdd(new UninitializedWritableDataBlobKey(name, size, alignment));
}

private NodeCache<ReadOnlyDataBlobKey, BlobNode> _readOnlyDataBlobs;

public BlobNode ReadOnlyDataBlob(Utf8String name, byte[] blobData, int alignment)
Expand Down Expand Up @@ -1205,5 +1217,26 @@ public ReadOnlyDataBlobKey(Utf8String name, byte[] data, int alignment)
public override bool Equals(object obj) => obj is ReadOnlyDataBlobKey && Equals((ReadOnlyDataBlobKey)obj);
public override int GetHashCode() => Name.GetHashCode();
}

protected struct UninitializedWritableDataBlobKey : IEquatable<UninitializedWritableDataBlobKey>
{
public readonly Utf8String Name;
public readonly int Size;
public readonly int Alignment;

public UninitializedWritableDataBlobKey(Utf8String name, int size, int alignment)
{
Name = name;
Size = size;
Alignment = alignment;
}

// The assumption here is that the name of the blob is unique.
// We can't emit two blobs with the same name and different contents.
// The name is part of the symbolic name and we don't do any mangling on it.
public bool Equals(UninitializedWritableDataBlobKey other) => Name.Equals(other.Name);
public override bool Equals(object obj) => obj is UninitializedWritableDataBlobKey && Equals((UninitializedWritableDataBlobKey)obj);
public override int GetHashCode() => Name.GetHashCode();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,17 @@ public bool IsStandardSection
{
get
{
return this == DataSection || this == ReadOnlyDataSection || this == FoldableReadOnlyDataSection || this == TextSection || this == XDataSection;
return this == DataSection || this == ReadOnlyDataSection || this == FoldableReadOnlyDataSection || this == TextSection || this == XDataSection || this == BssSection;
}
}

public static readonly ObjectNodeSection XDataSection = new ObjectNodeSection("xdata", SectionType.ReadOnly);
public static readonly ObjectNodeSection DataSection = new ObjectNodeSection("data", SectionType.Writeable);
public static readonly ObjectNodeSection ReadOnlyDataSection = new ObjectNodeSection("rdata", SectionType.ReadOnly);
public static readonly ObjectNodeSection FoldableReadOnlyDataSection = new ObjectNodeSection("rdata$F", SectionType.ReadOnly);
public static readonly ObjectNodeSection FoldableReadOnlyDataSection = new ObjectNodeSection("rdata", SectionType.ReadOnly);
public static readonly ObjectNodeSection TextSection = new ObjectNodeSection("text", SectionType.Executable);
public static readonly ObjectNodeSection TLSSection = new ObjectNodeSection("TLS", SectionType.Writeable);
public static readonly ObjectNodeSection BssSection = new ObjectNodeSection("bss", SectionType.Writeable);
public static readonly ObjectNodeSection ManagedCodeWindowsContentSection = new ObjectNodeSection(".managedcode$I", SectionType.Executable);
public static readonly ObjectNodeSection FoldableManagedCodeWindowsContentSection = new ObjectNodeSection(".managedcode$I", SectionType.Executable);
public static readonly ObjectNodeSection ManagedCodeUnixContentSection = new ObjectNodeSection("__managedcode", SectionType.Executable);
Expand Down
5 changes: 5 additions & 0 deletions src/Native/Runtime/inc/eetype.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ struct TypeManagerHandle;
class DynamicModule;
struct EETypeRef;

#if !defined(USE_PORTABLE_HELPERS)
#define SUPPORTS_WRITABLE_DATA 1
#endif

//-------------------------------------------------------------------------------------------------
// Array of these represents the interfaces implemented by a type

Expand Down Expand Up @@ -86,6 +90,7 @@ enum EETypeField
{
ETF_InterfaceMap,
ETF_TypeManagerIndirection,
ETF_WritableData,
ETF_Finalizer,
ETF_OptionalFieldsPtr,
ETF_SealedVirtualSlots,
Expand Down
9 changes: 9 additions & 0 deletions src/Native/Runtime/inc/eetype.inl
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,15 @@ __forceinline UInt32 EEType::GetFieldOffset(EETypeField eField)
}
cbOffset += relativeOrFullPointerOffset;

#if SUPPORTS_WRITABLE_DATA
// Followed by writable data.
if (eField == ETF_WritableData)
{
return cbOffset;
}
cbOffset += relativeOrFullPointerOffset;
#endif

// Followed by the pointer to the finalizer method.
if (eField == ETF_Finalizer)
{
Expand Down
2 changes: 1 addition & 1 deletion src/System.Private.CoreLib/shared/System/Nullable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ public static bool Equals<T>(Nullable<T> n1, Nullable<T> n2) where T : struct
{
if (nullableEEType.IsNullable)
{
return Internal.Reflection.Core.NonPortable.RuntimeTypeUnifier.GetRuntimeTypeForEEType(nullableEEType.NullableType);
return Type.GetTypeFromEETypePtr(nullableEEType.NullableType);
}
}
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,13 @@ internal static partial class RuntimeTypeUnifier
//
// Retrieves the unified Type object for given RuntimeTypeHandle (this is basically the Type.GetTypeFromHandle() api without the input validation.)
//
public static Type GetTypeForRuntimeTypeHandle(RuntimeTypeHandle runtimeTypeHandle)
=> RuntimeTypeHandleToTypeCache.Table.GetOrAdd(runtimeTypeHandle.RawValue);

internal static Type GetRuntimeTypeForEEType(EETypePtr eeType)
=> RuntimeTypeHandleToTypeCache.Table.GetOrAdd(eeType.RawValue);
{
// If writable data is supported, we shouldn't be using the hashtable - the runtime type
// is accessible through a couple indirections from the EETypePtr which is much faster.
Debug.Assert(!Internal.Runtime.EEType.SupportsWritableData);
return RuntimeTypeHandleToTypeCache.Table.GetOrAdd(eeType.RawValue);
}

//
// TypeTable mapping raw RuntimeTypeHandles (normalized or otherwise) to Types.
Expand All @@ -65,51 +67,54 @@ private RuntimeTypeHandleToTypeCache() { }
protected sealed override Type Factory(IntPtr rawRuntimeTypeHandleKey)
{
EETypePtr eeType = new EETypePtr(rawRuntimeTypeHandleKey);
RuntimeTypeHandle runtimeTypeHandle = new RuntimeTypeHandle(eeType);
return GetRuntimeTypeBypassCache(eeType);
}

public static readonly RuntimeTypeHandleToTypeCache Table = new RuntimeTypeHandleToTypeCache();
}

// Desktop compat: Allows Type.GetTypeFromHandle(default(RuntimeTypeHandle)) to map to null.
if (runtimeTypeHandle.IsNull)
return null;
// This bypasses the CoreLib's unifier, but there's another unifier deeper within the reflection stack:
// this code is safe to call without locking. See comment above.
public static Type GetRuntimeTypeBypassCache(EETypePtr eeType)
{
RuntimeTypeHandle runtimeTypeHandle = new RuntimeTypeHandle(eeType);

ReflectionExecutionDomainCallbacks callbacks = RuntimeAugments.Callbacks;
ReflectionExecutionDomainCallbacks callbacks = RuntimeAugments.Callbacks;

if (eeType.IsDefType)
{
if (eeType.IsGenericTypeDefinition)
{
return callbacks.GetNamedTypeForHandle(runtimeTypeHandle, isGenericTypeDefinition: true);
}
else if (eeType.IsGeneric)
{
return callbacks.GetConstructedGenericTypeForHandle(runtimeTypeHandle);
}
else
{
return callbacks.GetNamedTypeForHandle(runtimeTypeHandle, isGenericTypeDefinition: false);
}
}
else if (eeType.IsArray)
{
if (!eeType.IsSzArray)
return callbacks.GetMdArrayTypeForHandle(runtimeTypeHandle, eeType.ArrayRank);
else
return callbacks.GetArrayTypeForHandle(runtimeTypeHandle);
}
else if (eeType.IsPointer)
if (eeType.IsDefType)
{
if (eeType.IsGenericTypeDefinition)
{
return callbacks.GetPointerTypeForHandle(runtimeTypeHandle);
return callbacks.GetNamedTypeForHandle(runtimeTypeHandle, isGenericTypeDefinition: true);
}
else if (eeType.IsByRef)
else if (eeType.IsGeneric)
{
return callbacks.GetByRefTypeForHandle(runtimeTypeHandle);
return callbacks.GetConstructedGenericTypeForHandle(runtimeTypeHandle);
}
else
{
throw new ArgumentException(SR.Arg_InvalidRuntimeTypeHandle);
return callbacks.GetNamedTypeForHandle(runtimeTypeHandle, isGenericTypeDefinition: false);
}
}

public static readonly RuntimeTypeHandleToTypeCache Table = new RuntimeTypeHandleToTypeCache();
else if (eeType.IsArray)
{
if (!eeType.IsSzArray)
return callbacks.GetMdArrayTypeForHandle(runtimeTypeHandle, eeType.ArrayRank);
else
return callbacks.GetArrayTypeForHandle(runtimeTypeHandle);
}
else if (eeType.IsPointer)
{
return callbacks.GetPointerTypeForHandle(runtimeTypeHandle);
}
else if (eeType.IsByRef)
{
return callbacks.GetByRefTypeForHandle(runtimeTypeHandle);
}
else
{
throw new ArgumentException(SR.Arg_InvalidRuntimeTypeHandle);
}
}
}
}
Loading

0 comments on commit ec03519

Please sign in to comment.