How to make Activator.CreateInstance work about 20 times slower with a completely empty type

Given:

  • .NET assembly named expression_host
  • .NET assembly named CreateInstanceTest
  • CreateInstanceTest allows NetFx40_LegacySecurityPolicy in the configuration file
  • expression_host is attributed to SecurityPermission(SecurityAction.RequestOptional)
  • CreateInstanceTest loads the expression_host assembly - bang !!! - Activator.CreateInstance is a toast

Note:

 new() = 13, Activator.CreateInstance() = 111 Just loaded expression_host, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null new() = 6, Activator.CreateInstance() = 1944 

Explanation:

  • The program executes 500,000 times new TestClass() and 500,000 times Activator.CreateInstance(typeof(TestClass))
  • Then it loads the expression_host assembly
  • He then repeats step 1.
  • The numbers are milliseconds.

Both builds are really small. Here is the code:

CreateInstanceTest

CreateInstanceTest.csproj

 <?xml version="1.0" encoding="utf-8"?> <Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> <PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <ProjectGuid>{83690315-C8AC-4C52-9CDD-334115F521C0}</ProjectGuid> <OutputType>Exe</OutputType> <RootNamespace>CreateInstanceTest</RootNamespace> <AssemblyName>CreateInstanceTest</AssemblyName> <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> <PlatformTarget>AnyCPU</PlatformTarget> <DebugSymbols>true</DebugSymbols> <DebugType>full</DebugType> <OutputPath>bin\$(Configuration)\</OutputPath> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> <Prefer32Bit>false</Prefer32Bit> <UseVSHostingProcess>false</UseVSHostingProcess> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <Optimize>false</Optimize> <DefineConstants>DEBUG;TRACE</DefineConstants> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <Optimize>true</Optimize> <DefineConstants>TRACE</DefineConstants> </PropertyGroup> <ItemGroup> <Reference Include="System" /> </ItemGroup> <ItemGroup> <Compile Include="Program.cs" /> </ItemGroup> <ItemGroup> <None Include="App.config"> <SubType>Designer</SubType> </None> </ItemGroup> <ItemGroup> <ProjectReference Include="..\..\Users\mkharitonov\Documents\Visual Studio 2012\Projects\CreateInstanceTest\expression_host\expression_host.csproj"> <Project>{01f4b604-d5a3-454f-aff7-e1f5c43d293e}</Project> <Name>expression_host</Name> </ProjectReference> </ItemGroup> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> </Project> 

Program.cs

 using System; using System.Diagnostics; namespace CreateInstanceTest { internal class TestClass { } internal class Program { public static void Main() { const int COUNT = 500000; long newTime; long createInstanceTime; DoOneRound(COUNT, out newTime, out createInstanceTime); Console.WriteLine("new() = {0}, Activator.CreateInstance() = {1}", newTime, createInstanceTime); ScrewThingsUp(); DoOneRound(COUNT, out newTime, out createInstanceTime); Console.WriteLine("new() = {0}, Activator.CreateInstance() = {1}", newTime, createInstanceTime); Console.WriteLine("Press any key to terminate ..."); Console.ReadKey(); } public static void DoOneRound(int count, out long newTime, out long createInstanceTime) { var sw = new Stopwatch(); sw.Start(); for (int index = 0; index < count; ++index) { // ReSharper disable ObjectCreationAsStatement new TestClass(); // ReSharper restore ObjectCreationAsStatement } sw.Stop(); newTime = sw.ElapsedMilliseconds; var type = typeof(TestClass); sw.Restart(); for (int index = 0; index < count; ++index) { Activator.CreateInstance(type); } sw.Stop(); createInstanceTime = sw.ElapsedMilliseconds; } private static void ScrewThingsUp() { Console.WriteLine("Just loaded {0}", typeof(ReportExprHostImpl).Assembly.FullName); } } } 

app.config

 <?xml version="1.0" encoding="utf-8" ?> <configuration> <runtime> <NetFx40_LegacySecurityPolicy enabled="true"/> </runtime> </configuration> 

expression_host

expression_host.csproj

 <?xml version="1.0" encoding="utf-8"?> <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Build"> <PropertyGroup> <SchemaVersion>2.0</SchemaVersion> <ProjectGuid>{01F4B604-D5A3-454F-AFF7-E1F5C43D293E}</ProjectGuid> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <AssemblyName>expression_host</AssemblyName> <OutputType>Library</OutputType> <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> <DebugSymbols>true</DebugSymbols> <OutputPath>bin\$(Configuration)\</OutputPath> <DebugType>full</DebugType> <PlatformTarget>AnyCPU</PlatformTarget> <ErrorReport>prompt</ErrorReport> <Prefer32Bit>false</Prefer32Bit> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'"> <DefineConstants>DEBUG;TRACE</DefineConstants> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|AnyCPU'"> <DefineConstants>TRACE</DefineConstants> <Optimize>true</Optimize> </PropertyGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <ItemGroup> <Compile Include="ReportExprHostImpl.cs" /> </ItemGroup> </Project> 

ReportExprHostImpl.cs

 using System.Security.Permissions; [assembly: SecurityPermission(SecurityAction.RequestOptional)] public class ReportExprHostImpl { } 

What is it.

Now what is the question? The question is how to deal with this. This is actually the most minimal reproduction of a real life event and is associated with the case of the terrible degradation of the performance of Activator.CreateInstance

In a real application, we need to run reports, and we get stuck with NetFx40_LegacySecurityPolicy , which makes some parts of our application horrible.

Finally, in a real application, we cannot change the assembly code of expression nodes because they are dynamically created using the Microsoft reporting environment.

So what can we do?

EDIT

Adding [assembly: SecurityTransparent] to the CreateInstanceTest assembly improves:

 new() = 6, Activator.CreateInstance() = 106 Just loaded expression_host, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null new() = 14, Activator.CreateInstance() = 904 

Now Activator.CreateInstance works about 9 times slower, not 20. But still, 9 times slower!

EDIT 2

This seems to be an internal .NET thing. Managed Profiler (ANTS) is not very useful.

Other methods affected:

  • MethodInfo.Invoke (same behavior as Activator.CreateInstance )
  • Calling a compiled lambda expression
  • The call of the new operator for the general type of arguments - becase new T() compiled into Activator.CreateInstance<T>() - bummer.
  • The dynamic method emitted by Reflection.Emit with the owner null - see the overload of DynamicMethod , which takes an owner parameter.

We could not solve this problem, but in our case, replacing Activator.CreateInstance dynamically emitted constructor (with an owner type) solved our problem. This is a workaround, as the problem remains for other methods.

EDIT 1

By the way, we contacted Microsoft support on this issue, which turned out to be a waste of time. The best we could get from them is how this happens.

+6
source share
1 answer

Perhaps this would help: http://ayende.com/blog/3167/creating-objects-perf-implications

I would also try using ConstructorInfo myself, instead of calling Activator.CreateInstance before porting it and emitting the building code (I don’t remember if this is what Activator.CreateInstance does or not, however, since this is part of the proposed solution I I would try before moving on).

0
source

All Articles