Przypadek fatalnej degradacji wydajności Activator.CreateInstance
Ciekawe zachowanie obserwujemy w naszej aplikacji po stronie serwera .NET.
Praca związana z CPU / pamięcią zwalnia z czasem. Użyliśmy PerfView, aby spróbować zlokalizować sprawcę i wygląda na toActivator.CreateInstance
ten właściwy.
Jak go znaleźliśmy? Wykonaliśmy tę samą pracę na początku nowej sesji i po wykonaniu około 3000 raportów. W pierwszym przypadku PerfView nawet się nie pokazałActivator.CreateInstance
- jego odsetek był zbyt bliski 0% (ale zapewniono, że został wywołany). W drugim przypadku wykazało 28%.
Więc o co chodzi?Activator.CreateInstance
?
Używamy .NET 4.5
EDYTOWAĆ
Używamy domyślnej wersji konstrukcyjnejActivator.CreateInstance
.
EDYCJA 2
Ważny rozwój. Udało nam się zawęzić sprawę do zastosowania ram raportowania Microsoft w połączeniu z włączaniemNetFx40_LegacySecurityPolicy
.
W rzeczywistości mamy małe wywołanie testoweActivator.CreateInstance
przed i po wygenerowaniu małego raportu. Proszę znaleźć poniżej wyjścia:
new() = 4, Activator.CreateInstance() = 38
Building one report ... done.
new() = 13, Activator.CreateInstance() = 2261
Oznacza to, że to powołanieActivator.CreateInstance()
500 000 razy przed wygenerowaniem raportu zajęło tylko 38 milisekund.Po małym raporcie zabrało to 59 razy więcej!
Oto cały kod:
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>
Zwróć uwagę, że wyłączenieNetFx40_LegacySecurityPolicy
w pliku konfiguracyjnym robi cuda:
new() = 7, Activator.CreateInstance() = 106
Building one report ... done.
new() = 7, Activator.CreateInstance() = 78
Niestety, utknęliśmyNetFx40_LegacySecurityPolicy
z innych powodów, więc wyłączenie go nie jest opcją.
Każdy wkład jest najbardziej pożądany.
EDYCJA 3
Jak uruchomić Activator.CreateInstance około 20 razy wolniej przy całkowicie pustym typie