Prestaties van Object.GetType()

We hebben veel logging-oproepen in onze app. Onze logger gebruikt een System.Type-parameter zodat deze kan laten zien welke component de oproep heeft gemaakt. Soms, als we gestoord kunnen worden, doen we zoiets als:

class Foo
{
  private static readonly Type myType = typeof(Foo);
  void SomeMethod()
  {
     Logger.Log(myType, "SomeMethod started...");
  }
 }

Omdat dit vereist dat het Type-object slechts één keer wordt opgehaald. We hebben hier echter geen echte statistieken over. Heeft iemand enig idee hoeveel dit bespaart ten opzichte van het aanroepen van this.GetType() elke keer dat we inloggen?

(Ik realiseer me dat ik de statistieken zelf zou kunnen doen zonder grote problemen, maar hey, waar is StackOverflow voor?)


Antwoord 1, autoriteit 100%

Ik vermoed sterk dat GetType() aanzienlijk minder tijd zal kosten dan het daadwerkelijke loggen. Natuurlijk is er de mogelijkheid dat uw oproep naar Logger.Log geen daadwerkelijke IO zal doen… Ik vermoed nog steeds dat het verschil niet relevant zal zijn.

BEWERK: Benchmarkcode staat onderaan. Resultaten:

typeof(Test): 2756ms
TestType (field): 1175ms
test.GetType(): 3734ms

Dat is de methode 100 miljoenkeer aanroepen – de optimalisatie wint een paar seconden of zo. Ik vermoed dat de echte logmethode veel meer werk zal hebben, en dat 100 miljoen keer bellen in totaal veel langer dan 4 seconden zal duren, zelfs als er niets wordt weggeschreven. (Ik kan het natuurlijk mis hebben – dat zou je zelf moeten proberen.)

Met andere woorden, ik zou, zoals gewoonlijk, kiezen voor de meest leesbare code in plaats van micro-optimalisatie.

using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
class Test
{
    const int Iterations = 100000000;
    private static readonly Type TestType = typeof(Test);
    static void Main()
    {
        int total = 0;
        // Make sure it's JIT-compiled
        Log(typeof(Test)); 
        Stopwatch sw = Stopwatch.StartNew();
        for (int i = 0; i < Iterations; i++)
        {
            total += Log(typeof(Test));
        }
        sw.Stop();
        Console.WriteLine("typeof(Test): {0}ms", sw.ElapsedMilliseconds);
        sw = Stopwatch.StartNew();
        for (int i = 0; i < Iterations; i++)
        {
            total += Log(TestType);
        }
        sw.Stop();
        Console.WriteLine("TestType (field): {0}ms", sw.ElapsedMilliseconds);
        Test test = new Test();
        sw = Stopwatch.StartNew();
        for (int i = 0; i < Iterations; i++)
        {
            total += Log(test.GetType());
        }
        sw.Stop();
        Console.WriteLine("test.GetType(): {0}ms", sw.ElapsedMilliseconds);
    }
    // I suspect your real Log method won't be inlined,
    // so let's mimic that here
    [MethodImpl(MethodImplOptions.NoInlining)]
    static int Log(Type type)
    {
        return 1;
    }
}

Antwoord 2, autoriteit 20%

De functie GetType()is gemarkeerd met het speciale attribuut [MethodImpl(MethodImplOptions.InternalCall)]. Dit betekent dat de body van de methode geen IL bevat, maar in plaats daarvan een hook is in de interne onderdelen van de .NET CLR. In dit geval kijkt het naar de binaire structuur van de metadata van het object en maakt er een System.Type-object omheen.

EDIT:ik denk dat ik het mis had over iets …

Ik zei dat: “omdat voor GetType()een nieuw object moet worden gebouwd”, maar het lijkt erop dat dit niet correct is. Op de een of andere manier slaat de CLR het Typein de cache op en retourneert altijd hetzelfde object, zodat er geen nieuw Type-object hoeft te worden gemaakt.

Ik ben gebaseerd op de volgende test:

Object o1 = new Object();
Type t1 = o1.GetType();
Type t2 = o1.GetType();
if (object.ReferenceEquals(t1,t2))
    Console.WriteLine("same reference");

Dus ik verwacht niet veel winst bij uw implementatie.


Antwoord 3, autoriteit 9%

Ik betwijfel of je een bevredigend antwoord krijgt van SO over dit onderwerp. De reden hiervoor is dat prestaties, met name scenario’s van dit type, zeer toepassingsspecifiek zijn.

Iemand kan iets posten met een snelle stopwatch, waarvan het voorbeeld sneller zou zijn in termen van onbewerkte milliseconden. Maar eerlijk gezegd betekent dat niets voor uw toepassing. Waarom? Het hangt sterk af van het gebruikspatroon rond dat specifieke scenario. Bijvoorbeeld …

  1. Hoeveel soorten heb je?
  2. Hoe groot zijn jullie methoden?
  3. Doe je dit voor elke methode, of alleen voor de grote?

Dit zijn slechts enkele van de vragen die de relevantie van een directe tijdbenchmark aanzienlijk zullen veranderen.


Antwoord 4, autoriteit 3%

Heb je overwogen om nameofte gebruiken telefoniste?


Antwoord 5, autoriteit 2%

Het verschil is waarschijnlijk te verwaarlozen wat betreft de applicatieprestaties. Maar de eerste benadering waarbij u het type in de cache plaatst, zou sneller moeten zijn. Laten we gaan testen.

Deze code laat je het verschil zien:

using System;
namespace ConsoleApplicationTest {
    class Program {
        static void Main(string[] args) {
            int loopCount = 100000000;
            System.Diagnostics.Stopwatch timer1 = new System.Diagnostics.Stopwatch();
            timer1.Start();
            Foo foo = new Foo();
            for (int i = 0; i < loopCount; i++) {
                bar.SomeMethod();
            }
            timer1.Stop();
            Console.WriteLine(timer1.ElapsedMilliseconds);
            System.Diagnostics.Stopwatch timer2 = new System.Diagnostics.Stopwatch();
            timer2.Start();
            Bar bar = new Bar();
            for (int i = 0; i < loopCount; i++) {
                foo.SomeMethod();
            }
            timer2.Stop();
            Console.WriteLine(timer2.ElapsedMilliseconds);
            Console.ReadLine();
        }
    }
    public class Bar {
        public void SomeMethod() {
            Logger.Log(this.GetType(), "SomeMethod started...");
        }
    }
    public class Foo {
        private static readonly Type myType = typeof(Foo); 
        public void SomeMethod() { 
            Logger.Log(myType, "SomeMethod started..."); 
        }
    }
    public class Logger {
        public static void Log(Type type, string text) {
        }
    }
}

Op mijn machine gaf dit resultaten van ongeveer. 1500 milliseconden voor de eerste nadering en ca. 2200 milliseconden voor de tweede.

(code en timing gecorrigeerd – doh!)


Antwoord 6

het gebruik van een veld is de beste manier en het voorkomt interne woordenboekvergrendeling waardoor door typeof() en GetType() een unieke referentie behouden blijft.

Other episodes