我们在.NET服务器端应用程序中观察到一种有趣的行为。
CPU /内存限制工作随着时间的推移而变慢。我们使用PerfView来尝试找出罪魁祸首,似乎Activator.CreateInstance
就是那个。
我们是如何找到它的?我们在新会议开始时和执行大约3,000份报告后执行相同的工作。在第一种情况下,PerfView甚至没有显示Activator.CreateInstance
- 它的百分比太接近0%(但可以肯定它被称为)。对于第二种情况,它显示了28%。
那么,与Activator.CreateInstance
的交易是什么?
我们正在使用.NET 4.5
修改
我们使用的是Activator.CreateInstance
的默认构建版本。
编辑2
一个重要的发展。我们已经设法将案例缩小到使用Microsoft报告框架并启用NetFx40_LegacySecurityPolicy
。
事实上,在生成一个小报告之前和之后,我们有一个小的测试用例调用Activator.CreateInstance
。请在下面找到输出:
new() = 4, Activator.CreateInstance() = 38
Building one report ... done.
new() = 13, Activator.CreateInstance() = 2261
这意味着在生成报告之前调用Activator.CreateInstance()
500,000次只需要38毫秒。 在微小的报告之后它花费了59倍!
以下是整个代码:
Program.cs的
using System;
using System.Diagnostics;
using System.IO;
using Microsoft.Reporting.WebForms;
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);
Console.Write("Building one report ... ");
Console.Out.Flush();
RunReport();
Console.WriteLine("done.");
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 RunReport()
{
var localReport = new LocalReport();
localReport.LoadReportDefinition(new StringReader(
"<?xml version=\"1.0\" encoding=\"utf-8\"?>"+
"<Report xmlns=\"http://schemas.microsoft.com/sqlserver/reporting/2008/01/reportdefinition\">"+
" <Body>"+
" <Height>1in</Height>"+
" </Body>"+
" <Width>1in</Width>"+
" <Page>"+
" <PageFooter>"+
" <Height>1in</Height>"+
" <ReportItems>"+
" <Textbox Name=\"OverallTotalPages\">"+
" <Paragraphs>"+
" <Paragraph>"+
" <TextRuns>"+
" <TextRun>"+
" <Value>=Globals!OverallTotalPages</Value>"+
" </TextRun>"+
" </TextRuns>"+
" </Paragraph>"+
" </Paragraphs>"+
" </Textbox>"+
" </ReportItems>"+
" </PageFooter>"+
" </Page>"+
"</Report>"
));
localReport.Render("pdf");
}
}
}
的App.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<runtime>
<NetFx40_LegacySecurityPolicy enabled="true"/>
</runtime>
</configuration>
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="Microsoft.ReportViewer.Common, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
<Reference Include="Microsoft.ReportViewer.WebForms, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
<Reference Include="System" />
<Reference Include="System.Core" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config">
<SubType>Designer</SubType>
</None>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
注意,在配置文件中禁用NetFx40_LegacySecurityPolicy
会产生奇迹:
new() = 7, Activator.CreateInstance() = 106
Building one report ... done.
new() = 7, Activator.CreateInstance() = 78
不幸的是,由于其他原因,我们仍然遇到NetFx40_LegacySecurityPolicy
,所以禁用它不是一种选择。
欢迎任何意见。
编辑3
How to make Activator.CreateInstance run about 20 times slower with a completely empty type