【发布时间】:2014-01-17 16:46:46
【问题描述】:
给定:
- .NET 程序集名为
expression_host - .NET 程序集名为
CreateInstanceTest - CreateInstanceTest 在其配置文件中启用 NetFx40_LegacySecurityPolicy
- expression_host 属性为
SecurityPermission(SecurityAction.RequestOptional) - CreateInstanceTest 加载 expression_host 程序集 - bang!!! -
Activator.CreateInstanceis toast
请观察输出:
new() = 13, Activator.CreateInstance() = 111
Just loaded expression_host, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
new() = 6, Activator.CreateInstance() = 1944
解释:
- 程序执行500,000次
new TestClass()和500,000次Activator.CreateInstance(typeof(TestClass)) - 然后加载 expression_host 程序集
- 然后它重复步骤 1。
- 数字以毫秒为单位。
这两个程序集都非常小。代码如下:
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.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
{
}
就是这样。
现在的问题是什么?问题是如何处理它。这其实是一个真实案例的最小再现,和A case of abysmal degradation of Activator.CreateInstance performance有关
在实际应用程序中,我们必须运行报告,并且我们被NetFx40_LegacySecurityPolicy 卡住了,这使得我们应用程序的某些部分表现得很糟糕。
最后,在实际应用程序中,我们无法更改表达式宿主程序集的代码,因为它们是由 Microsoft 报告框架动态构建的。
那么我们能做些什么呢?
编辑
将[assembly: SecurityTransparent] 添加到 CreateInstanceTest 程序集似乎有所改善:
new() = 6, Activator.CreateInstance() = 106
Just loaded expression_host, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
new() = 14, Activator.CreateInstance() = 904
现在Activator.CreateInstance 只慢了大约 9 倍,而不是 20 倍。但仍然慢了 9 倍!
编辑 2
这似乎是内部 .NET 的事情。托管分析器 (ANTS) 不是很有用。
其他受影响的方法:
-
MethodInfo.Invoke(与Activator.CreateInstance行为相同) - 调用已编译的 lambda 表达式
- 在泛型参数类型上调用
new运算符 - 因为new T()被编译为Activator.CreateInstance<T>()- 太糟糕了。 - 使用 Reflection.Emit 和
null所有者发出的动态方法 - 请参阅接受owner参数的DynamicMethod重载。
我们未能解决这个问题,但在我们的例子中,用动态发出的构造函数(具有所有者类型)替换 Activator.CreateInstance 解决了我们的问题。这是一种解决方法,因为其他方法仍然存在问题。
编辑 1
顺便说一句,我们就这个问题联系了 Microsoft 支持,结果证明这完全是浪费时间。我们能从他们那里得到的最好的结果就是它就是这样。
【问题讨论】:
-
有趣。尝试分析代码。如果通常的 .NET 分析器过于高级,请尝试 perfview。这不应该隐藏任何东西。
-
结果最令人困惑,没有多大意义。 PerfView 不够好,我们尝试了 ANTS profiler。我们仍在挖掘它。
-
这似乎是一个内部 .NET 的东西 - 托管分析器不是很有用。
标签: .net