web-dev-qa-db-fra.com

Champ vs propriété. Optimisation des performances

Veuillez noter cette question liée aux performances uniquement. Permet d'ignorer les directives de conception, la philosophie, la compatibilité, la portabilité et tout ce qui n'est pas lié aux performances pures. Je vous remercie.

Passons maintenant à la question. J'ai toujours supposé que parce que les getters/setters C # sont vraiment des méthodes déguisées, la lecture du champ public doit être plus rapide que d'appeler un getter.

Donc pour m'assurer d'avoir fait un test (le code ci-dessous). Cependant, ce test ne produit que les résultats attendus (c'est-à-dire les champs sont plus rapides que les getters à 34%) if vous l'exécutez depuis Visual Studio.

Une fois que vous l'exécutez à partir de la ligne de commande, il affiche à peu près le même timing ...

La seule explication pourrait être que le CLR fait une optimisation supplémentaire (corrigez-moi si je me trompe ici).

Je ne crois pas que dans une application réelle où ces propriétés sont utilisées de manière beaucoup plus sophistiquée, elles seront optimisées de la même manière.

S'il vous plaît, aidez-moi à prouver ou à réfuter l'idée que dans la vraie vie les propriétés sont plus lentes que les champs.

La question est - comment dois-je modifier les classes de test pour faire changer le comportement du CLR afin que le champ public surpasse les getters. OR montre-moi que toute propriété sans logique interne effectuera la comme un champ (au moins sur le getter)

EDIT: Je ne parle que de la version Release 64.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace PropertyVsField
{
    class Program
    {
        static int LEN = 20000000;
        static void Main(string[] args)
        {
            List<A> a = new List<A>(LEN);
            List<B> b = new List<B>(LEN);

            Random r = new Random(DateTime.Now.Millisecond);

            for (int i = 0; i < LEN; i++)
            {
                double p = r.NextDouble();
                a.Add(new A() { P = p });
                b.Add(new B() { P = p });
            }

            Stopwatch sw = new Stopwatch();

            double d = 0.0;

            sw.Restart();
            for (int i = 0; i < LEN; i++)
            {
                d += a[i].P;
            }

            sw.Stop();

            Console.WriteLine("auto getter. {0}. {1}.", sw.ElapsedTicks, d);

            sw.Restart();
            for (int i = 0; i < LEN; i++)
            {
                d += b[i].P;
            }

            sw.Stop();

            Console.WriteLine("      field. {0}. {1}.", sw.ElapsedTicks, d);

            Console.ReadLine();
        }
    }

    class A
    {
        public double P { get; set; }
    }
    class B
    {
        public double P;
    }
}
63
Boppity Bop

Comme d'autres l'ont déjà mentionné, les getters sont en ligne.

Si vous voulez éviter l'incrustation, vous devez

  • remplacez les propriétés automatiques par des propriétés manuelles:

    class A 
    {
        private double p;
        public double P
        {
            get { return p; }
            set { p = value; }
        }
    } 
    
  • et dites au compilateur de ne pas aligner le getter (ou les deux, si vous en avez envie):

            [MethodImpl(MethodImplOptions.NoInlining)]
            get { return p; }
    

Notez que la première modification ne fait pas de différence dans les performances, tandis que la deuxième modification montre une surcharge claire d'appel de méthode:

Propriétés manuelles:

auto getter. 519005. 10000971,0237547.
      field. 514235. 20001942,0475098.

Pas d'incrustation du getter:

auto getter. 785997. 10000476,0385552.
      field. 531552. 20000952,077111.
53
Heinzi

Jetez un oeil à l'article de blog Propriétés vs Champs - Pourquoi est-ce important? (Jonathan Aneja) de l'un des membres de l'équipe VB sur MSDN. Il décrit la propriété par rapport à l'argument champs et explique également les propriétés triviales comme suit:

Un argument que j'ai entendu pour utiliser des champs sur des propriétés est que "les champs sont plus rapides", mais pour les propriétés triviales, ce n'est pas vrai, car le compilateur Just-In-Time (JIT) du CLR alignera l'accès à la propriété et générera du code qui est aussi efficace que l'accès direct à un champ.

24
JamieSee

Le JIT alignera toute méthode (pas seulement un getter) qui, selon ses mesures internes, sera plus rapidement intégrée. Étant donné qu'une propriété standard est return _Property; il sera inséré dans tous les cas.

La raison pour laquelle vous voyez un comportement différent est qu'en mode débogage avec un débogueur attaché, le JIT est considérablement handicapé, pour garantir que tous les emplacements de pile correspondent à ce que vous attendez du code.

Vous oubliez également la règle numéro un de la performance, testant la réflexion. Par exemple, même si le tri rapide est asymptotiquement plus rapide que le tri par insertion, le tri par insertion est en fait plus rapide pour les entrées extrêmement petites.

12
Guvante

La seule explication pourrait être que le CLR fait une optimisation supplémentaire (corrigez-moi si je me trompe ici).

Oui, c'est ce qu'on appelle l'inline. Cela se fait dans le compilateur (niveau du code machine - c'est-à-dire JIT). Comme le getter/setter est trivial (c'est-à-dire un code très simple), les appels de méthode sont détruits et le getter/setter écrit dans le code environnant.

Cela ne se produit pas en mode débogage afin de prendre en charge le débogage (c'est-à-dire la possibilité de définir un point d'arrêt dans un getter ou un setter).

Dans Visual Studio, il n'y a aucun moyen de le faire dans le débogueur. Compilez la version, exécutez sans débogueur attaché et vous obtiendrez l'optimisation complète.

Je ne crois pas que dans une application réelle où ces propriétés sont utilisées de manière beaucoup plus sophistiquée, elles seront optimisées de la même manière.

Le monde est plein d'illusions qui ont tort. Ils seront optimisés car ils sont encore triviaux (c'est-à-dire du code simple, donc ils sont en ligne).

6
TomTom

Il convient de noter qu'il est possible de voir les "vraies" performances dans Visual Studio.

  1. Compilez en mode Release avec les optimisations activées.
  2. Accédez à Déboguer -> Options et paramètres et décochez "Supprimer l'optimisation JIT lors de la charge du module (géré uniquement)".
  3. Si vous le souhaitez, décochez "Activer juste mon code", sinon vous ne pourrez peut-être pas entrer le code.

Maintenant, l'assemblage jitted sera le même même avec le débogueur attaché, vous permettant de participer au démontage optimisé si vous le souhaitez. Ceci est essentiel pour comprendre comment le CLR optimise le code.

3
Asik

Après avoir lu tous vos articles, je décide de faire un benchmark avec ces codes:

    [TestMethod]
    public void TestFieldVsProperty()
    {
        const int COUNT = 0x7fffffff;
        A a1 = new A();
        A a2 = new A();
        B b1 = new B();
        B b2 = new B();
        C c1 = new C();
        C c2 = new C();
        D d1 = new D();
        D d2 = new D();
        Stopwatch sw = new Stopwatch();

        long t1, t2, t3, t4;

        sw.Restart();
        for (int i = COUNT - 1; i >= 0; i--)
        {
            a1.P = a2.P;
        }

        sw.Stop();

        t1 = sw.ElapsedTicks;

        sw.Restart();
        for (int i = COUNT - 1; i >= 0; i--)
        {
            b1.P = b2.P;
        }

        sw.Stop();


        t2 = sw.ElapsedTicks;

        sw.Restart();
        for (int i = COUNT - 1; i >= 0; i--)
        {
            c1.P = c2.P;
        }

        sw.Stop();

        t3 = sw.ElapsedTicks;

        sw.Restart();
        for (int i = COUNT - 1; i >= 0; i--)
        {
            d1.P = d2.P;
        }

        sw.Stop();


        t4 = sw.ElapsedTicks;
        long max = Math.Max(Math.Max(t1, t2), Math.Max(t3, t4));

        Console.WriteLine($"auto: {t1}, {max * 100d / t1:00.00}%.");
        Console.WriteLine($"field: {t2}, {max * 100d / t2:00.00}%.");
        Console.WriteLine($"manual: {t3}, {max * 100d / t3:00.00}%.");
        Console.WriteLine($"no inlining: {t4}, {max * 100d / t4:00.00}%.");

    }
    class A
    {
        public double P { get; set; }
    }
    class B
    {
        public double P;
    }
    class C
    {
        private double p;
        public double P
        {
            get => p;
            set => p = value;
        }
    }
    class D
    {
        public double P
        {
            [MethodImpl(MethodImplOptions.NoInlining)]
            get;
            [MethodImpl(MethodImplOptions.NoInlining)]
            set;
        }
    }

Lors d'un test en mode débogage, j'ai obtenu ce résultat:

auto: 35142496, 100.78%.
field: 10451823, 338.87%.
manual: 35183121, 100.67%.
no inlining: 35417844, 100.00%.

mais lorsque vous passez en mode de libération, le résultat est différent qu'auparavant.

auto: 2161291, 873.91%.
field: 2886444, 654.36%.
manual: 2252287, 838.60%.
no inlining: 18887768, 100.00%.

semble que la propriété automatique est une meilleure façon.

0
dexiang