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

questionAnswers(0)

yourAnswerToTheQuestion