C'è uno svantaggio nell'usare AggressiveInlining su proprietà semplici?

10

Scommetto che potrei rispondere a me stesso se sapessi di più sugli strumenti per analizzare come si comporta C # / JIT, ma dal momento che non lo faccio, ti prego di sopportare che me lo chieda.

Ho un codice semplice come questo:

    private SqlMetaData[] meta;

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

Come puoi vedere, ho messo AggressiveInlining perché ritengo che dovrebbe essere in linea.
Credo. Non c'è alcuna garanzia che il JIT lo indichi diversamente. Mi sbaglio?

È possibile che questo tipo di cose faccia male alle prestazioni / alla stabilità / a qualcosa?

    
posta Serge 23.06.2014 - 17:28
fonte

4 risposte

17

I compilatori sono bestie intelligenti. Di solito, spremeranno automaticamente tutte le prestazioni possibili da qualsiasi luogo.

Cercare di superare in astuzia il compilatore di solito non fa una grande differenza e ha molte possibilità di ritorcersi contro. Ad esempio, l'inlining rende il programma più grande in quanto duplica il codice ovunque. Se la tua funzione è utilizzata in molti punti del codice, potrebbe essere dannosa come indicato in @CodesInChaos. Se è ovvio che la funzione dovrebbe essere in linea, puoi scommettere che il compilatore lo farà.

In caso di esitazione, puoi ancora fare entrambe le cose e confrontare se c'è qualche guadagno in termini di prestazioni, questo è l'unico modo certo per ora. Ma la mia scommessa è che la differenza sarà neglegabile, il codice sorgente sarà solo "più rumoroso".

    
risposta data 23.06.2014 - 17:41
fonte
6

Hai ragione - non c'è modo di garantire che il metodo sia sottolineato - MSDN Enumerazione MethodImplOptions , SO MethodImplOptions.AggressiveInlining vs TargetedPatchingOptOut .

I programmatori sono più intelligenti di un compilatore, ma lavoriamo su un livello più alto e le nostre ottimizzazioni sono i prodotti del lavoro di un uomo - il nostro. Jitter vede cosa sta succedendo durante l'esecuzione. Può analizzare sia il flusso di esecuzione che il codice in base alle conoscenze fornite dai suoi progettisti. Puoi conoscere meglio il tuo programma, ma conoscono meglio il CLR. E chi sarà più corretto nelle sue ottimizzazioni? Non lo sappiamo per certo.

Ecco perché dovresti testare qualsiasi ottimizzazione tu faccia. Anche se è molto semplice. E prendi in considerazione che l'ambiente potrebbe cambiare e l'ottimizzazione o la disoptimizzazione può avere un risultato abbastanza inaspettato.

    
risposta data 23.06.2014 - 17:44
fonte
6

EDIT: Mi rendo conto che la mia risposta non ha risposto esattamente alla domanda, mentre non c'è un vero svantaggio, dai miei risultati di cronometraggio non c'è nemmeno un vero vantaggio. La differenza tra un getter di proprietà in linea è di 0,002 secondi su 500 milioni di iterazioni. Il mio test case potrebbe anche non essere accurato al 100% dal momento che usa una struct perché ci sono alcuni avvertimenti sul jitter e l'inline con le strutture.

Come sempre, l'unico modo per sapere davvero è scrivere un test e capirlo. Ecco i miei risultati con la seguente configurazione:

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

Svuota il progetto con le seguenti impostazioni:

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

Risultati

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

Testato con questo codice:

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);
        }
    }
}
    
risposta data 03.12.2014 - 00:38
fonte
3

I compilatori fanno molte ottimizzazioni. Inlining è uno di questi, indipendentemente dal programmatore desiderato. Ad esempio, MethodImplOptions non ha un'opzione "in linea". Perché l'inline viene automaticamente eseguito dal compilatore, se necessario.

Molte altre ottimizzazioni sono fatte specialmente se abilitate dalle opzioni di build, o la modalità "release" lo farà. Ma queste ottimizzazioni sono tipo "ha funzionato per te, grande! Non ha funzionato, lascialo" ottimizzazioni e di solito offrono prestazioni migliori.

[MethodImpl(MethodImplOptions.AggressiveInlining)]

è solo una bandiera per il compilatore che una operazione di allineamento è davvero desiderata qui. Maggiori informazioni qui e qui

Per rispondere alla tua domanda;

There's no guarantee the JIT would inline it otherwise. Am I wrong?

È vero. Nessuna garanzia; Né C # ha un'opzione di "forzatura della linea".

Could doing this kind of thing hurt the performance/stability/anything?

In questo caso no, come si dice in scrittura di applicazioni gestite ad alte prestazioni: un primer

Property get and set methods are generally good candidates for inlining, since all they do is typically initialize private data members.

    
risposta data 17.08.2015 - 16:13
fonte

Leggi altre domande sui tag