【问题标题】:How to make Activator.CreateInstance run about 20 times slower with a completely empty type如何使用完全空的类型使 Activator.CreateInstance 运行速度慢约 20 倍
【发布时间】:2014-01-17 16:46:46
【问题描述】:

给定:

  • .NET 程序集名为 expression_host
  • .NET 程序集名为 CreateInstanceTest
  • CreateInstanceTest 在其配置文件中启用 NetFx40_LegacySecurityPolicy
  • expression_host 属性为SecurityPermission(SecurityAction.RequestOptional)
  • CreateInstanceTest 加载 expression_host 程序集 - bang!!! - Activator.CreateInstance is toast

请观察输出:

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

解释:

  1. 程序执行500,000次new TestClass()和500,000次Activator.CreateInstance(typeof(TestClass))
  2. 然后加载 expression_host 程序集
  3. 然后它重复步骤 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.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&lt;T&gt;() - 太糟糕了。
  • 使用 Reflection.Emit 和 null 所有者发出的动态方法 - 请参阅接受 owner 参数的 DynamicMethod 重载。

我们未能解决这个问题,但在我们的例子中,用动态发出的构造函数(具有所有者类型)替换 Activator.CreateInstance 解决了我们的问题。这是一种解决方法,因为其他方法仍然存在问题。

编辑 1

顺便说一句,我们就这个问题联系了 Microsoft 支持,结果证明这完全是浪费时间。我们能从他们那里得到的最好的结果就是它就是这样。

【问题讨论】:

  • 有趣。尝试分析代码。如果通常的 .NET 分析器过于高级,请尝试 perfview。这不应该隐藏任何东西。
  • 结果最令人困惑,没有多大意义。 PerfView 不够好,我们尝试了 ANTS profiler。我们仍在挖掘它。
  • 这似乎是一个内部 .NET 的东西 - 托管分析器不是很有用。

标签: .net


【解决方案1】:

也许这会有所帮助:http://ayende.com/blog/3167/creating-objects-perf-implications

我也会尝试单独使用ConstructorInfo,而不是在包装它并发出构造代码之前调用Activator.CreateInstance(我不记得Activator.CreateInstance是否这样做,因为它是建议的解决方案我会在继续之前尝试过)。

【讨论】:

  • 问题不在于Activator.CreateInstance 本身。问题在于,当加载带有NetFx40_LegacySecurityPolicy 的程序集时,它的性能会急剧下降。并且它不是唯一受到影响的反射相关方法。现在,我们可以用其他东西替换Activator.CreateInstance,但这不是问题的重点。顺便说一句,new TT 是泛型类型时也会受到影响,因为new T 被编译器转换为Activator.CreateInstance&lt;T&gt;
猜你喜欢
  • 2021-03-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-06
  • 1970-01-01
  • 2020-10-03
相关资源
最近更新 更多