it-swarm.com.de

Gibt es einen Nachteil bei der Verwendung von AggressiveInlining für einfache Eigenschaften?

Ich wette, ich könnte das selbst beantworten, wenn ich mehr über Tools zur Analyse des Verhaltens von C #/JIT wüsste, aber da ich dies nicht tue, muss ich bitte fragen.

Ich habe einfachen Code wie diesen:

    private SqlMetaData[] meta;

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

Wie Sie sehen können, habe ich AggressiveInlining verwendet, weil ich der Meinung bin, dass es inline sein sollte.
Ich glaube. Es gibt keine Garantie, dass die JIT es sonst inline würde. Liege ich falsch?

Könnte so etwas die Leistung/Stabilität/irgendetwas beeinträchtigen?

17
Serge

Compiler sind kluge Biester. Normalerweise werden sie automatisch so viel Leistung wie möglich von überall aus herausholen.

Der Versuch, den Compiler zu überlisten, macht normalerweise keinen großen Unterschied und hat viele Chancen, nach hinten loszugehen. Zum Beispiel vergrößert Inlining Ihr Programm, da es den Code überall dupliziert. Wenn Ihre Funktion an vielen Stellen im Code verwendet wird, ist sie möglicherweise tatsächlich nachteilig, wie unter @CodesInChaos ausgeführt. Wenn es offensichtlich ist, dass die Funktion inline sein sollte, können Sie darauf wetten, dass der Compiler dies tut.

Im Falle eines Zögerns können Sie immer noch beides tun und vergleichen, ob es einen Leistungsgewinn gibt. Dies ist der einzig sichere Weg bis jetzt. Aber ich wette, der Unterschied wird vernachlässigbar sein, der Quellcode wird nur "lauter" sein.

22
dagnelies

Sie haben Recht - es gibt keine Möglichkeit zu garantieren, dass die Methode inline ist - MSDN MethodImplOptions Enumeration , SO MethodImplOptions.AggressiveInlining vs TargetedPatchingOptOut .

Programmierer sind intelligenter als ein Compiler, aber wir arbeiten auf einer höheren Ebene und unsere Optimierungen sind Produkte der Arbeit eines Mannes - unserer eigenen. Jitter sieht, was während der Hinrichtung los ist. Es kann sowohl den Ausführungsfluss als auch den Code nach dem Wissen seiner Designer analysieren. Sie können Ihr Programm besser kennen, aber sie kennen die CLR besser. Und wer wird bei seinen Optimierungen korrekter sein? Wir wissen es nicht genau.

Deshalb sollten Sie jede von Ihnen vorgenommene Optimierung testen. Auch wenn es sehr einfach ist. Und berücksichtigen Sie, dass sich die Umgebung ändern kann und Ihre Optimierung oder Disoptimierung zu einem unerwarteten Ergebnis führen kann.

8
Eugene Podskal

EDIT: Mir ist klar, dass meine Antwort die Frage nicht genau beantwortet hat, obwohl es keinen wirklichen Nachteil gibt. Aufgrund meiner Timing-Ergebnisse gibt es auch keinen wirklichen Vorteil. Der Unterschied zwischen einem Inline-Property-Getter beträgt 0,002 Sekunden über 500 Millionen Iterationen. Mein Testfall ist möglicherweise auch nicht 100% genau, da er eine Struktur verwendet, da der Jitter und das Inlining mit Strukturen einige Einschränkungen aufweisen.

Wie immer ist der einzige Weg, es wirklich zu wissen, einen Test zu schreiben und es herauszufinden. Hier sind meine Ergebnisse mit der folgenden Konfiguration:

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

Projekt mit folgenden Einstellungen leeren:

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

Ergebnisse

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

Getestet mit diesem 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

Compiler führen viele Optimierungen durch. Inlining ist eine davon, ob der Programmierer wollte oder nicht. Beispielsweise verfügt MethodImplOptions nicht über eine "Inline" -Option. Weil das Inlining bei Bedarf automatisch vom Compiler durchgeführt wird.

Viele andere Optimierungen werden insbesondere durchgeführt, wenn sie über die Build-Optionen aktiviert sind oder der "Release" -Modus dies tut. Aber diese Optimierungen sind eine Art "für Sie gearbeitet, großartig! Nicht funktioniert, lassen Sie es" -Optimierungen und bieten normalerweise eine bessere Leistung.

[MethodImpl(MethodImplOptions.AggressiveInlining)]

ist nur ein Flag für den Compiler, dass hier wirklich eine Inlining-Operation gewünscht wird. Weitere Infos hier und hier

Zur Beantwortung Ihrer Frage;

Es gibt keine Garantie, dass die JIT es sonst einbinden würde. Liege ich falsch?

Wahr. Keine Garantie; Weder C # hat eine Option "Force Inlining".

Könnte so etwas die Leistung/Stabilität/irgendetwas beeinträchtigen?

In diesem Fall nein, wie in Schreiben von verwalteten Hochleistungsanwendungen: Ein Primer gesagt

Property Get- und Set-Methoden sind im Allgemeinen gute Kandidaten für Inlining, da sie in der Regel nur private Datenelemente initialisieren.

5
myuce