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) {
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.