web-dev-qa-db-fra.com

Y a-t-il un inconvénient à utiliser AggressiveInlining sur des propriétés simples?

Je parie que je pourrais répondre moi-même si j'en savais plus sur les outils pour analyser le comportement de C #/JIT, mais comme je ne le fais pas, veuillez me le demander.

J'ai un code simple comme celui-ci:

    private SqlMetaData[] meta;

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private SqlMetaData[] Meta
    {
        get
        {
            return this.meta;
        }
    }

Comme vous pouvez le voir, j'ai mis AggressiveInlining parce que je pense qu'il devrait être intégré.
Je pense. Il n'y a aucune garantie que le JIT l'inclurait autrement. Ai-je tort?

Est-ce que faire ce genre de chose pourrait nuire aux performances/stabilité/quoi que ce soit?

17
Serge

Les compilateurs sont des bêtes intelligentes. Habituellement, ils extraient automatiquement autant de performances que possible de n'importe où.

Essayer de déjouer le compilateur ne fait généralement pas une grande différence et a beaucoup de chances de se retourner. Par exemple, l'inlining agrandit votre programme car il duplique le code partout. Si votre fonction est utilisée à de nombreux endroits dans le code, elle pourrait en fait être nuisible, comme l'a souligné @CodesInChaos. S'il est évident que la fonction doit être intégrée, vous pouvez parier que le compilateur le fera.

En cas d'hésitation, vous pouvez toujours faire les deux et comparer s'il y a un gain de performance, c'est le seul moyen certain pour l'instant. Mais mon pari est que la différence sera infaillible, le code source sera juste "plus bruyant".

22
dagnelies

Vous avez raison - il n'y a aucun moyen de garantir que la méthode sera intégrée - MSDN énumération MethodImplOptions , SO MethodImplOptions.AggressiveInlining vs TargetedPatchingOptOut .

Les programmeurs sont plus intelligents qu'un compilateur, mais nous travaillons à un niveau supérieur et nos optimisations sont le produit du travail d'un seul homme - le nôtre. Jitter voit ce qui se passe pendant l'exécution. Il peut analyser à la fois le flux d'exécution et le code en fonction des connaissances qui y sont apportées par ses concepteurs. Vous pouvez mieux connaître votre programme, mais ils connaissent mieux le CLR. Et qui sera plus correct dans ses optimisations? Nous ne savons pas avec certitude.

C'est pourquoi vous devriez tester toute optimisation que vous faites. Même si c'est très simple. Et tenez compte du fait que l'environnement peut changer et que votre optimisation ou désoptimisation peut avoir un résultat assez inattendu.

8
Eugene Podskal

EDIT: Je me rends compte que ma réponse n'a pas répondu exactement à la question, alors qu'il n'y a pas de réel inconvénient, d'après mes résultats temporels, il n'y a pas non plus de réel avantage. La différence entre un getter de propriété en ligne est de 0,002 seconde sur 500 millions d'itérations. Mon cas de test peut également ne pas être précis à 100% car il utilise une structure, car il y a des mises en garde à propos de la gigue et de l'inclusion de structures.

Comme toujours, la seule façon de vraiment savoir est d'écrire un test et de le comprendre. Voici mes résultats avec la configuration suivante:

Windows 7 Home  
8GB ram  
64bit os  
i5-2300 2.8ghz  

Projet vide avec les paramètres suivants:

.NET 4.5  
Release mode  
Start without debugger attached - CRUCIAL  
Unchecked "Prefer 32-bit" under project build settings  

Résultats

struct get property                               : 0.3097832 seconds
struct inline get property                        : 0.3079076 seconds
struct method call with params                    : 1.0925033 seconds
struct inline method call with params             : 1.0930666 seconds
struct method call without params                 : 1.5211852 seconds
struct intline method call without params         : 1.2235001 seconds

Testé avec ce code:

class Program
{
    const int SAMPLES = 5;
    const int ITERATIONS = 100000;
    const int DATASIZE = 1000;

    static Random random = new Random();
    static Stopwatch timer = new Stopwatch();
    static Dictionary<string, TimeSpan> timings = new Dictionary<string, TimeSpan>();

    class SimpleTimer : IDisposable
    {
        private string name;
        public SimpleTimer(string name)
        {
            this.name = name;
            timer.Restart();
        }

        public void Dispose()
        {
            timer.Stop();
            TimeSpan ts = TimeSpan.Zero;
            if (timings.ContainsKey(name))
                ts = timings[name];

            ts += timer.Elapsed;
            timings[name] = ts;
        }
    }

    [StructLayout(LayoutKind.Sequential, Size = 4)]
    struct TestStruct
    {
        private int x;
        public int X { get { return x; } set { x = value; } }
    }


    [StructLayout(LayoutKind.Sequential, Size = 4)]
    struct TestStruct2
    {
        private int x;

        public int X
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            get { return x; }
            set { x = value; }
        }
    }

    [StructLayout(LayoutKind.Sequential, Size = 8)]
    struct TestStruct3
    {
        private int x;
        private int y;

        public void Update(int _x, int _y)
        {
            x += _x;
            y += _y;
        }
    }

    [StructLayout(LayoutKind.Sequential, Size = 8)]
    struct TestStruct4
    {
        private int x;
        private int y;

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Update(int _x, int _y)
        {
            x += _x;
            y += _y;
        }
    }

    [StructLayout(LayoutKind.Sequential, Size = 8)]
    struct TestStruct5
    {
        private int x;
        private int y;

        public void Update()
        {
            x *= x;
            y *= y;
        }
    }

    [StructLayout(LayoutKind.Sequential, Size = 8)]
    struct TestStruct6
    {
        private int x;
        private int y;

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Update()
        {
            x *= x;
            y *= y;
        }
    }

    static void RunTests()
    {
        for (var i = 0; i < SAMPLES; ++i)
        {
            Console.Write("Sample {0} ... ", i);
            RunTest1();
            RunTest2();
            RunTest3();
            RunTest4();
            RunTest5();
            RunTest6();
            Console.WriteLine(" complate");
        }
    }

    static int RunTest1()
    {
        var data = new TestStruct[DATASIZE];
        var temp = 0;
        unchecked
        {
            //init the data, just so jitter can't make assumptions
            for (var j = 0; j < DATASIZE; ++j)
                data[j].X = random.Next();

            using (new SimpleTimer("struct get property"))
            {
                for (var j = 0; j < DATASIZE; ++j)
                {
                    for (var i = 0; i < ITERATIONS; ++i)
                    {
                        //use some math to make sure its not optimized out (aka don't use an incrementor)
                        temp += data[j].X;
                    }
                }
            }
        }
        //again need variables to cross scopes to make sure the jitter doesn't do crazy optimizations
        return temp;
    }

    static int RunTest2()
    {
        var data = new TestStruct2[DATASIZE];
        var temp = 0;
        unchecked
        {
            //init the data, just so jitter can't make assumptions
            for (var j = 0; j < DATASIZE; ++j)
                data[j].X = random.Next();

            using (new SimpleTimer("struct inline get property"))
            {
                for (var j = 0; j < DATASIZE; ++j)
                {
                    for (var i = 0; i < ITERATIONS; ++i)
                    {
                        //use some math to make sure its not optimized out (aka don't use an incrementor)
                        temp += data[j].X;
                    }
                }
            }
        }
        //again need variables to cross scopes to make sure the jitter doesn't do crazy optimizations
        return temp;
    }

    static void RunTest3()
    {
        var data = new TestStruct3[DATASIZE];
        unchecked
        {
            using (new SimpleTimer("struct method call with params"))
            {
                for (var j = 0; j < DATASIZE; ++j)
                {
                    for (var i = 0; i < ITERATIONS; ++i)
                    {
                        //use some math to make sure its not optimized out (aka don't use an incrementor)
                        data[j].Update(j, i);
                    }
                }
            }
        }
    }

    static void RunTest4()
    {
        var data = new TestStruct4[DATASIZE];
        unchecked
        {
            using (new SimpleTimer("struct inline method call with params"))
            {
                for (var j = 0; j < DATASIZE; ++j)
                {
                    for (var i = 0; i < ITERATIONS; ++i)
                    {
                        //use some math to make sure its not optimized out (aka don't use an incrementor)
                        data[j].Update(j, i);
                    }
                }
            }
        }
    }

    static void RunTest5()
    {
        var data = new TestStruct5[DATASIZE];
        unchecked
        {
            using (new SimpleTimer("struct method call without params"))
            {
                for (var j = 0; j < DATASIZE; ++j)
                {
                    for (var i = 0; i < ITERATIONS; ++i)
                    {
                        //use some math to make sure its not optimized out (aka don't use an incrementor)
                        data[j].Update();
                    }
                }
            }
        }
    }

    static void RunTest6()
    {
        var data = new TestStruct6[DATASIZE];
        unchecked
        {
            using (new SimpleTimer("struct intline method call without params"))
            {
                for (var j = 0; j < DATASIZE; ++j)
                {
                    for (var i = 0; i < ITERATIONS; ++i)
                    {
                        //use some math to make sure its not optimized out (aka don't use an incrementor)
                        data[j].Update();
                    }
                }
            }
        }
    }

    static void Main(string[] args)
    {
        RunTests();
        DumpResults();
        Console.Read();
    }

    static void DumpResults()
    {
        foreach (var kvp in timings)
        {
            Console.WriteLine("{0,-50}: {1} seconds", kvp.Key, kvp.Value.TotalSeconds);
        }
    }
}
8
Chris Phillips

Les compilateurs font beaucoup d'optimisations. Inline est l'un d'entre eux, que le programmeur le veuille ou non. Par exemple, MethodImplOptions n'a pas d'option "en ligne". Parce que l'inline est fait automatiquement par le compilateur si nécessaire.

De nombreuses autres optimisations sont spécialement effectuées si elles sont activées à partir des options de construction, ou le mode "release" le fera. Mais ces optimisations sont en quelque sorte des optimisations "travaillées pour vous, super! Non travaillées, laissez-le" et donnent généralement de meilleures performances.

[MethodImpl(MethodImplOptions.AggressiveInlining)]

est juste un indicateur pour le compilateur qu'une opération d'inline est vraiment souhaitée ici. Plus d'infos ici et ici

Pour répondre à ta question;

Il n'y a aucune garantie que le JIT l'inline autrement. Ai-je tort?

Vrai. Aucune garantie; Aucun C # n'a une option "forcer l'inline".

Est-ce que faire ce genre de chose pourrait nuire aux performances/stabilité/quoi que ce soit?

Dans ce cas, non, comme il est dit dans Writing High-Performance Managed Applications: A Primer

Les méthodes get et set de propriété sont généralement de bons candidats pour l'inlining, car elles ne font généralement qu'initialiser les membres de données privées.

5
myuce