2014-01-17 65 views
6

由於運行約20倍慢:如何使Activator.CreateInstance一個完全空型

  • .NET程序集命名爲expression_host
  • 命名爲CreateInstanceTest
  • CreateInstanceTest .NET組件使得NetFx40_LegacySecurityPolicy在其配置文件
  • expression_host歸功於SecurityPermission(SecurityAction.RequestOptional)
  • CreateInstanceTest加載expression_host程序集 - 砰! - Activator.CreateInstance祝酒

請觀察輸出:

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

說明:

  1. 程序沒有50萬次new TestClass()和50萬次Activator.CreateInstance(typeof(TestClass))
  2. 然後加載expression_host組裝
  3. Th en重複步驟1.
  4. 這些數字是毫秒。

兩個組件都很小。下面是代碼:

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 
{ 
} 

就是這樣。

現在是什麼問題?問題是如何處理它。這實際上是一個真實案例的最起碼的繁殖和有關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倍!

EDIT 2

這似乎是一個內部.NET的事情。託管分析器(ANTS)不是很有用。

其他受影響的方法:

  • MethodInfo.Invoke(相同的行爲Activator.CreateInstance
  • 調用編譯lambda表達式
  • 調用上通用的參數類型new運營商 - becase的new T()被編譯成Activator.CreateInstance<T>() - 無賴。
  • 用Reflection.Emit與null所有者一起發射的動態方法 - 請參閱DynamicMethod過載接受owner參數。

我們未能解決這個問題,但在我們的案例中用動態發出的構造函數(擁有者類型)替換Activator.CreateInstance解決了我們的問題。這是一種解決方法,因爲問題仍然存在於其他方法中。

編輯1

順便說一句,我們在這個問題上,這竟然是完全是浪費時間聯繫了微軟的支持。我們從他們身上得到的最好的結果就是這樣。

+0

有趣。嘗試分析代碼。如果通常的.NET分析器太高級嘗試perfview。這應該隱藏什麼。 – usr

+0

結果是最令人困惑,沒有多大意義。 PerfView不夠好,我們嘗試了ANTS分析器。我們仍在挖掘它。 – mark

+0

這似乎是一個內部.NET的事情 - 管理的分析器不是很有用。 – mark

回答

0

也許這將有助於:http://ayende.com/blog/3167/creating-objects-perf-implications

我也嘗試使用它自己的ConstructorInfo,而不是包裝,併發射構造代碼之前調用Activator.CreateInstance(我不記得是否這就是Activator.CreateInstance做還是不做,還因爲它是建議的解決方案的一部分,所以在繼續之前我會嘗試它)。

+1

問題本身不是Activator.CreateInstance。問題在於,當加載具有'NetFx40_LegacySecurityPolicy'的程序集時,其性能突然劇烈下降。而且這不是唯一與反射相關的方法受到影響。現在,我們可以用'Activator.CreateInstance'替換其他東西,但這不是問題的焦點。順便說一句,當'T'是泛型類型時'new T'也受到影響,因爲'new T'被編譯器轉換爲'Activator.CreateInstance '。 – mark

相關問題