Skip to content

Commit 9a1dc54

Browse files
committed
Improved uninitialized object creation performance
1 parent b94a82d commit 9a1dc54

File tree

8 files changed

+162
-34
lines changed

8 files changed

+162
-34
lines changed

src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -209,12 +209,12 @@
209209
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\RuntimeHelpers.CoreCLR.cs" />
210210
<Compile Include="$(BclSourcesRoot)\System\Runtime\ControlledExecution.CoreCLR.cs" />
211211
<Compile Include="$(BclSourcesRoot)\System\Runtime\DependentHandle.cs" />
212-
<Compile Include="$(MSBuildThisFileDirectory)..\nativeaot\Common\src\System\Runtime\RhFailFastReason.cs" Condition="('$(Platform)' != 'x86' or '$(TargetsWindows)' != 'true')"/>
213-
<Compile Include="$(MSBuildThisFileDirectory)..\nativeaot\Runtime.Base\src\System\Runtime\ExceptionHandling.cs" Condition="('$(Platform)' != 'x86' or '$(TargetsWindows)' != 'true')"/>
214-
<Compile Include="$(MSBuildThisFileDirectory)..\nativeaot\Runtime.Base\src\System\Runtime\ExceptionIDs.cs" Condition="('$(Platform)' != 'x86' or '$(TargetsWindows)' != 'true')"/>
215-
<Compile Include="$(MSBuildThisFileDirectory)..\nativeaot\Runtime.Base\src\System\Runtime\StackFrameIterator.cs" Condition="('$(Platform)' != 'x86' or '$(TargetsWindows)' != 'true')"/>
216-
<Compile Include="$(BclSourcesRoot)\System\Runtime\ExceptionServices\AsmOffsets.cs" Condition="('$(Platform)' != 'x86' or '$(TargetsWindows)' != 'true')"/>
217-
<Compile Include="$(BclSourcesRoot)\System\Runtime\ExceptionServices\InternalCalls.cs" Condition="('$(Platform)' != 'x86' or '$(TargetsWindows)' != 'true')"/>
212+
<Compile Include="$(MSBuildThisFileDirectory)..\nativeaot\Common\src\System\Runtime\RhFailFastReason.cs" Condition="('$(Platform)' != 'x86' or '$(TargetsWindows)' != 'true')" />
213+
<Compile Include="$(MSBuildThisFileDirectory)..\nativeaot\Runtime.Base\src\System\Runtime\ExceptionHandling.cs" Condition="('$(Platform)' != 'x86' or '$(TargetsWindows)' != 'true')" />
214+
<Compile Include="$(MSBuildThisFileDirectory)..\nativeaot\Runtime.Base\src\System\Runtime\ExceptionIDs.cs" Condition="('$(Platform)' != 'x86' or '$(TargetsWindows)' != 'true')" />
215+
<Compile Include="$(MSBuildThisFileDirectory)..\nativeaot\Runtime.Base\src\System\Runtime\StackFrameIterator.cs" Condition="('$(Platform)' != 'x86' or '$(TargetsWindows)' != 'true')" />
216+
<Compile Include="$(BclSourcesRoot)\System\Runtime\ExceptionServices\AsmOffsets.cs" Condition="('$(Platform)' != 'x86' or '$(TargetsWindows)' != 'true')" />
217+
<Compile Include="$(BclSourcesRoot)\System\Runtime\ExceptionServices\InternalCalls.cs" Condition="('$(Platform)' != 'x86' or '$(TargetsWindows)' != 'true')" />
218218
<Compile Include="$(BclSourcesRoot)\System\Runtime\GCSettings.CoreCLR.cs" />
219219
<Compile Include="$(BclSourcesRoot)\System\Runtime\JitInfo.CoreCLR.cs" />
220220
<Compile Include="$(BclSourcesRoot)\System\Runtime\InteropServices\ComTypes\IEnumerable.cs" />
@@ -248,7 +248,8 @@
248248
<Compile Include="$(BclSourcesRoot)\System\ValueType.cs" />
249249
<Compile Include="$(CommonPath)System\Collections\Generic\ArrayBuilder.cs">
250250
<Link>Common\System\Collections\Generic\ArrayBuilder.cs</Link>
251-
</Compile>
251+
</Compile>
252+
<Compile Include="src\System\RuntimeType.CreateUninitializedCache.CoreCLR.cs" />
252253
</ItemGroup>
253254
<ItemGroup Condition="'$(FeatureComWrappers)' == 'true'">
254255
<Compile Include="$(BclSourcesRoot)\System\Runtime\InteropServices\ComWrappers.cs" />
@@ -328,11 +329,7 @@
328329
<Compile Include="@(EventingSourceFile)" />
329330
</ItemGroup>
330331

331-
<Target Name="GenerateEventingFiles"
332-
Inputs="@(EventingGenerationScript);@(EventManifestFile)"
333-
Outputs="@(EventingSourceFile)"
334-
DependsOnTargets="FindPython"
335-
BeforeTargets="BeforeCompile">
332+
<Target Name="GenerateEventingFiles" Inputs="@(EventingGenerationScript);@(EventManifestFile)" Outputs="@(EventingSourceFile)" DependsOnTargets="FindPython" BeforeTargets="BeforeCompile">
336333

337334
<Error Condition="'$(PYTHON)' == ''" Text="Unable to locate Python. NativeRuntimeEventSource.CoreCLR.cs cannot be generated without Python installed on the machine. Either install Python in your path or point to it with the PYTHON environment variable." />
338335
<PropertyGroup>

src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -188,17 +188,13 @@ public static object GetUninitializedObject(
188188
if (type is not RuntimeType rt)
189189
{
190190
ArgumentNullException.ThrowIfNull(type);
191-
throw new SerializationException(SR.Format(SR.Serialization_InvalidType, type));
191+
static void Throw(Type type) => throw new SerializationException(SR.Format(SR.Serialization_InvalidType, type));
192+
Throw(type);
192193
}
193194

194-
object? obj = null;
195-
GetUninitializedObject(new QCallTypeHandle(ref rt), ObjectHandleOnStack.Create(ref obj));
196-
return obj!;
195+
return rt.GetUninitializedObject();
197196
}
198197

199-
[LibraryImport(QCall, EntryPoint = "ReflectionSerialization_GetUninitializedObject")]
200-
private static partial void GetUninitializedObject(QCallTypeHandle type, ObjectHandleOnStack retObject);
201-
202198
[MethodImpl(MethodImplOptions.InternalCall)]
203199
internal static extern object AllocateUninitializedClone(object obj);
204200

src/coreclr/System.Private.CoreLib/src/System/RuntimeType.ActivatorCache.cs

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ private sealed unsafe class ActivatorCache
2424
private readonly delegate*<object?, void> _pfnCtor;
2525
private readonly bool _ctorIsPublic;
2626

27+
private CreateUninitializedCache? _createUninitializedCache;
28+
2729
#if DEBUG
2830
private readonly RuntimeType _originalRuntimeType;
2931
#endif
@@ -110,21 +112,36 @@ static void CtorNoopStub(object? uninitializedObject) { }
110112
// as the object itself will keep the type alive.
111113

112114
#if DEBUG
113-
if (_originalRuntimeType != rt)
114-
{
115-
Debug.Fail("Caller passed the wrong RuntimeType to this routine."
116-
+ Environment.NewLineConst + "Expected: " + (_originalRuntimeType ?? (object)"<null>")
117-
+ Environment.NewLineConst + "Actual: " + (rt ?? (object)"<null>"));
118-
}
115+
CheckOriginalRuntimeType(rt);
119116
#endif
120-
121117
object? retVal = _pfnAllocator(_allocatorFirstArg);
122118
GC.KeepAlive(rt);
123119
return retVal;
124120
}
125121

126122
[MethodImpl(MethodImplOptions.AggressiveInlining)]
127123
internal void CallConstructor(object? uninitializedObject) => _pfnCtor(uninitializedObject);
124+
125+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
126+
internal CreateUninitializedCache GetCreateUninitializedCache(RuntimeType rt)
127+
{
128+
#if DEBUG
129+
CheckOriginalRuntimeType(rt);
130+
#endif
131+
return _createUninitializedCache ??= new CreateUninitializedCache(rt);
132+
}
133+
134+
#if DEBUG
135+
private void CheckOriginalRuntimeType(RuntimeType rt)
136+
{
137+
if (_originalRuntimeType != rt)
138+
{
139+
Debug.Fail("Caller passed the wrong RuntimeType to this routine."
140+
+ Environment.NewLineConst + "Expected: " + (_originalRuntimeType ?? (object)"<null>")
141+
+ Environment.NewLineConst + "Actual: " + (rt ?? (object)"<null>"));
142+
}
143+
}
144+
#endif
128145
}
129146
}
130147
}

src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3879,6 +3879,29 @@ private void CreateInstanceCheckThis()
38793879
}
38803880
}
38813881

3882+
/// <summary>
3883+
/// Helper to get instances of uninitialized objects.
3884+
/// </summary>
3885+
[DebuggerStepThrough]
3886+
[DebuggerHidden]
3887+
internal object GetUninitializedObject()
3888+
{
3889+
if (GenericCache is not CreateUninitializedCache cache)
3890+
{
3891+
if (GenericCache is ActivatorCache activatorCache)
3892+
{
3893+
cache = activatorCache.GetCreateUninitializedCache(this);
3894+
}
3895+
else
3896+
{
3897+
cache = new CreateUninitializedCache(this);
3898+
GenericCache = cache;
3899+
}
3900+
}
3901+
3902+
return cache.CreateUninitializedObject(this);
3903+
}
3904+
38823905
/// <summary>
38833906
/// Helper to invoke the default (parameterless) constructor.
38843907
/// </summary>
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Diagnostics;
5+
using System.Runtime.CompilerServices;
6+
using System.Runtime.InteropServices;
7+
8+
namespace System
9+
{
10+
internal sealed partial class RuntimeType
11+
{
12+
/// <summary>
13+
/// A cache which allows optimizing <see cref="RuntimeHelpers.GetUninitializedObject(Type)"/>.
14+
/// </summary>
15+
private sealed unsafe partial class CreateUninitializedCache
16+
{
17+
// The managed calli to the newobj allocator, plus its first argument (MethodTable*).
18+
private readonly delegate*<void*, object> _pfnAllocator;
19+
private readonly void* _allocatorFirstArg;
20+
21+
#if DEBUG
22+
private readonly RuntimeType _originalRuntimeType;
23+
#endif
24+
25+
internal CreateUninitializedCache(RuntimeType rt)
26+
{
27+
Debug.Assert(rt != null);
28+
29+
#if DEBUG
30+
_originalRuntimeType = rt;
31+
#endif
32+
33+
GetCreateUninitializedInfo(rt, out _pfnAllocator, out _allocatorFirstArg);
34+
}
35+
36+
internal object CreateUninitializedObject(RuntimeType rt)
37+
{
38+
#if DEBUG
39+
if (_originalRuntimeType != rt)
40+
{
41+
Debug.Fail("Caller passed the wrong RuntimeType to this routine."
42+
+ Environment.NewLineConst + "Expected: " + (_originalRuntimeType ?? (object)"<null>")
43+
+ Environment.NewLineConst + "Actual: " + (rt ?? (object)"<null>"));
44+
}
45+
#endif
46+
object retVal = _pfnAllocator(_allocatorFirstArg);
47+
GC.KeepAlive(rt);
48+
return retVal;
49+
}
50+
51+
/// <summary>
52+
/// Given a RuntimeType, returns information about how to create uninitialized instances
53+
/// of it via calli semantics.
54+
/// </summary>
55+
private static void GetCreateUninitializedInfo(
56+
RuntimeType rt,
57+
out delegate*<void*, object> pfnAllocator,
58+
out void* vAllocatorFirstArg)
59+
{
60+
Debug.Assert(rt != null);
61+
62+
delegate*<void*, object> pfnAllocatorTemp = default;
63+
void* vAllocatorFirstArgTemp = default;
64+
65+
GetCreateUninitializedInfo(
66+
new QCallTypeHandle(ref rt),
67+
&pfnAllocatorTemp, &vAllocatorFirstArgTemp);
68+
69+
pfnAllocator = pfnAllocatorTemp;
70+
vAllocatorFirstArg = vAllocatorFirstArgTemp;
71+
}
72+
73+
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ReflectionSerialization_GetCreateUninitializedObjectInfo")]
74+
private static partial void GetCreateUninitializedInfo(
75+
QCallTypeHandle type,
76+
delegate*<void*, object>* ppfnAllocator,
77+
void** pvAllocatorFirstArg);
78+
}
79+
}
80+
}

src/coreclr/vm/qcallentrypoints.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ static const Entry s_QCall[] =
314314
DllImportEntry(ReflectionInvocation_RunModuleConstructor)
315315
DllImportEntry(ReflectionInvocation_CompileMethod)
316316
DllImportEntry(ReflectionInvocation_PrepareMethod)
317-
DllImportEntry(ReflectionSerialization_GetUninitializedObject)
317+
DllImportEntry(ReflectionSerialization_GetCreateUninitializedObjectInfo)
318318
#if defined(FEATURE_COMWRAPPERS)
319319
DllImportEntry(ComWrappers_GetIUnknownImpl)
320320
DllImportEntry(ComWrappers_TryGetComInstance)

src/coreclr/vm/reflectioninvocation.cpp

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1862,9 +1862,19 @@ FCIMPLEND
18621862
//*************************************************************************************************
18631863
//*************************************************************************************************
18641864
//*************************************************************************************************
1865-
extern "C" void QCALLTYPE ReflectionSerialization_GetUninitializedObject(QCall::TypeHandle pType, QCall::ObjectHandleOnStack retObject)
1865+
extern "C" void QCALLTYPE ReflectionSerialization_GetCreateUninitializedObjectInfo(
1866+
QCall::TypeHandle pType,
1867+
PCODE* ppfnAllocator,
1868+
void** pvAllocatorFirstArg)
18661869
{
1867-
QCALL_CONTRACT;
1870+
CONTRACTL{
1871+
QCALL_CHECK;
1872+
PRECONDITION(CheckPointer(ppfnAllocator));
1873+
PRECONDITION(CheckPointer(pvAllocatorFirstArg));
1874+
PRECONDITION(*ppfnAllocator == NULL);
1875+
PRECONDITION(*pvAllocatorFirstArg == NULL);
1876+
}
1877+
CONTRACTL_END;
18681878

18691879
BEGIN_QCALL;
18701880

@@ -1880,14 +1890,19 @@ extern "C" void QCALLTYPE ReflectionSerialization_GetUninitializedObject(QCall::
18801890
COMPlusThrow(kNotSupportedException, W("NotSupported_ManagedActivation"));
18811891
#endif // FEATURE_COMINTEROP
18821892

1883-
// If it is a nullable, return the underlying type instead.
1893+
// If it is a nullable, return the allocator for the underlying type instead.
18841894
if (pMT->IsNullable())
18851895
pMT = pMT->GetInstantiation()[0].GetMethodTable();
18861896

1897+
bool fHasSideEffectsUnused;
1898+
*ppfnAllocator = CEEJitInfo::getHelperFtnStatic(CEEInfo::getNewHelperStatic(pMT, &fHasSideEffectsUnused));
1899+
*pvAllocatorFirstArg = pMT;
1900+
1901+
pMT->EnsureInstanceActive();
1902+
1903+
if (pMT->HasPreciseInitCctors())
18871904
{
1888-
GCX_COOP();
1889-
// Allocation will invoke any precise static cctors as needed.
1890-
retObject.Set(pMT->Allocate());
1905+
pMT->CheckRunClassInitAsIfConstructingThrowing();
18911906
}
18921907

18931908
END_QCALL;

src/coreclr/vm/reflectioninvocation.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ extern "C" void QCALLTYPE ReflectionInvocation_RunModuleConstructor(QCall::Modul
7272

7373
extern "C" void QCALLTYPE ReflectionInvocation_PrepareMethod(MethodDesc* pMD, TypeHandle *pInstantiation, UINT32 cInstantiation);
7474

75-
extern "C" void QCALLTYPE ReflectionSerialization_GetUninitializedObject(QCall::TypeHandle pType, QCall::ObjectHandleOnStack retObject);
75+
extern "C" void QCALLTYPE ReflectionSerialization_GetCreateUninitializedObjectInfo(QCall::TypeHandle pType, PCODE* ppfnAllocator, void** pvAllocatorFirstArg);
7676

7777
class ReflectionEnum {
7878
public:

0 commit comments

Comments
 (0)