Um caso de degradação abismal do Activator.CreateInstance performance
Há um comportamento interessante que estamos observando em nosso aplicativo do lado do servidor .NET.
O trabalho vinculado CPU / Memória diminui com o tempo. Usamos o PerfView para tentar localizar o culpado e parece queActivator.CreateInstance
é aquele.
Como a encontramos? Executamos o mesmo trabalho no início de uma nova sessão e após a execução de cerca de 3.000 relatórios. No primeiro caso, o PerfView nem mostrouActivator.CreateInstance
- seu percentual estava muito próximo de 0% (mas tenha certeza de que foi chamado). Para o segundo caso, mostrou 28%.
Então, qual é o problema?Activator.CreateInstance
?
Estamos usando o .NET 4.5
EDITAR
Estamos usando a versão de construção padrão doActivator.CreateInstance
.
EDIT 2
Um desenvolvimento importante. Conseguimos restringir o caso ao uso da estrutura de relatórios da Microsoft em conjunto com a ativaçãoNetFx40_LegacySecurityPolicy
.
De fato, temos um pequeno caso de teste chamandoActivator.CreateInstance
antes e depois de gerar um pequeno relatório. Por favor, encontre abaixo a saída:
new() = 4, Activator.CreateInstance() = 38
Building one report ... done.
new() = 13, Activator.CreateInstance() = 2261
O que isso significa é que chamarActivator.CreateInstance()
500.000 vezes antes da geração do relatório, levou apenas 38 milissegundos.Após o pequeno relatório, foram necessárias 59 vezes mais!
Aqui está o código inteiro:
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>
Observe que desativarNetFx40_LegacySecurityPolicy
no arquivo de configuração faz milagres:
new() = 7, Activator.CreateInstance() = 106
Building one report ... done.
new() = 7, Activator.CreateInstance() = 78
Infelizmente, estamos presos aNetFx40_LegacySecurityPolicy
por outros motivos, desativá-lo não é uma opção.
Qualquer entrada é bem-vinda.
EDIT 3