Apakah ada kerugian menggunakan AggressiveInlining pada properti sederhana?


16

Saya bertaruh saya bisa menjawabnya sendiri jika saya tahu lebih banyak tentang alat untuk menganalisis bagaimana C # / JIT berperilaku, tetapi karena saya tidak, tolong tahan dengan saya bertanya.

Saya punya kode sederhana seperti ini:

    private SqlMetaData[] meta;

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

Seperti yang Anda lihat, saya meletakkan AggressiveInlining karena saya merasa seperti itu harus diuraikan.
Kupikir. Tidak ada jaminan bahwa JIT akan memberikannya sebaliknya. Apakah aku salah?

Mungkinkah melakukan hal semacam ini melukai kinerja / stabilitas / apa pun?


2
1) Dalam pengalaman saya metode primitif seperti itu akan diuraikan tanpa atribut. Saya terutama menemukan atribut yang berguna dengan metode non-sepele yang masih harus diuraikan. 2) Tidak ada jaminan bahwa metode yang didekorasi dengan atribut akan diuraikan juga. Itu hanya petunjuk bagi JITter.
CodesInChaos

Saya tidak tahu banyak tentang atribut inlining baru, tetapi menempatkan satu di sini hampir pasti tidak akan membuat perbedaan dalam kinerja. Yang Anda lakukan hanyalah mengembalikan referensi ke array, dan JIT hampir pasti sudah membuat pilihan yang benar di sini.
Robert Harvey

14
3) Memasukkan terlalu banyak berarti kode menjadi lebih besar, dan mungkin tidak cocok dengan cache lagi. Kehilangan cache bisa menjadi hit kinerja yang signifikan. 4) Saya sarankan untuk tidak menggunakan atribut sampai benchmark menunjukkan bahwa itu meningkatkan kinerja.
CodesInChaos

4
Berhenti mengkhawatirkan. Semakin Anda mencoba mengakali kompiler, semakin banyak ia akan menemukan cara untuk mengakali Anda. Temukan hal lain yang perlu dikhawatirkan.
david.pfx

1
Untuk dua sen saya, saya telah melihat keuntungan besar dalam mode rilis, terutama saat memanggil fungsi yang lebih besar dalam loop ketat.
jjxtra

Jawaban:


22

Kompiler adalah binatang pintar. Biasanya, mereka secara otomatis akan memeras kinerja sebanyak yang mereka bisa dari mana saja.

Mencoba mengakali kompiler biasanya tidak membuat perbedaan besar, dan memiliki banyak peluang untuk menjadi bumerang. Misalnya, inlining membuat program Anda lebih besar karena duplikat kode di mana-mana. Jika fungsi Anda digunakan di banyak tempat di seluruh kode, itu mungkin sebenarnya merugikan seperti yang ditunjukkan @CodesInChaos. Jika jelas fungsi tersebut harus diuraikan, Anda dapat bertaruh kompiler akan melakukannya.

Jika ragu, Anda masih bisa melakukan keduanya dan membandingkan jika ada keuntungan kinerja, itulah satu-satunya cara untuk sekarang. Tetapi taruhan saya adalah perbedaannya akan diabaikan, kode sumber hanya akan menjadi "lebih ribut".


3
Saya pikir "kebisingan" adalah poin paling penting di sini. Jaga kode Anda tetap rapi dan percayakan kompiler Anda untuk melakukan hal yang benar sampai terbukti sebaliknya. Yang lainnya adalah optimasi prematur yang berbahaya.
5gon12eder

1
Jika kompiler sangat pintar, lalu mengapa mencoba mengakali bumerang kompiler?
Little Endian

11
Kompiler tidak pintar . Compiler tidak melakukan "hal yang benar". Jangan mengaitkan intelijen di tempat yang tidak. Bahkan, C # compiler / JITer terlalu bodoh. Sebagai contoh, ia tidak akan menyinkronkan 32 byte IL atau kasus yang melibatkan structs sebagai parameter - di mana dalam banyak kasus seharusnya dan bisa. Selain kehilangan ratusan optimasi yang jelas - termasuk, tetapi tidak terbatas pada - menghindari batas cek yang tidak perlu dan alokasi antara hal-hal lain.
JBeurer

4
@DaveBlack Bounds memeriksa elusion dalam C # yang terjadi dalam daftar kasus yang sangat mendasar, biasanya pada urutan paling dasar untuk loop yang dilakukan, dan bahkan kemudian banyak loop sederhana gagal dioptimalkan. Loop array multi-dimensi tidak mendapatkan penghapusan batas cek, loop iterated dalam urutan menurun tidak, loop pada array yang baru dialokasikan tidak. Sangat banyak kasus sederhana di mana Anda mengharapkan kompiler melakukan pekerjaannya. Tapi ternyata tidak. Karena itu apa saja, tapi pintar. blogs.msdn.microsoft.com/clrcodegeneration/2009/08/13/...
JBeurer

3
Kompiler bukan "binatang pintar". Mereka hanya menerapkan banyak heuristik dan melakukan trade-off untuk mencoba dan menemukan keseimbangan untuk sebagian besar skenario yang diantisipasi oleh penulis kompiler. Saya sarankan membaca: docs.microsoft.com/en-us/previous-versions/dotnet/articles/…
cdiggins

8

Anda benar - tidak ada cara untuk menjamin bahwa metode ini akan diuraikan - MSDN MethodImplOptions Enumeration , SO MethodImplOptions.AggressiveInlining vs TargetedPatchingOptOut .

Programmer lebih cerdas daripada kompiler, tetapi kami bekerja pada level yang lebih tinggi dan optimisasi kami adalah produk dari pekerjaan satu orang - milik kami. Jitter melihat apa yang terjadi selama eksekusi. Ia dapat menganalisis alur eksekusi dan kode sesuai dengan pengetahuan yang dimasukkan ke dalamnya oleh perancang. Anda bisa mengetahui program Anda lebih baik, tetapi mereka lebih tahu CLR. Dan siapa yang akan lebih benar dalam pengoptimalannya? Kami tidak tahu pasti.

Itu sebabnya Anda harus menguji setiap optimasi yang Anda lakukan. Bahkan jika itu sangat sederhana. Dan perhatikan bahwa lingkungan dapat berubah dan optimasi atau disoptimasi Anda dapat memiliki hasil yang sangat tak terduga.


8

EDIT: Saya menyadari jawaban saya tidak benar-benar menjawab pertanyaan, sementara tidak ada kelemahan nyata, dari hasil waktu saya tidak ada terbalik nyata juga. Perbedaan antara pengambil properti inline adalah 0,002 detik lebih dari 500 juta iterasi. Kasing pengujian saya mungkin juga tidak 100% akurat karena menggunakan struct karena ada beberapa peringatan terhadap jitter dan inlining dengan struct.

Seperti biasa, satu-satunya cara untuk benar-benar tahu adalah menulis tes dan mencari tahu. Ini hasil saya dengan konfigurasi sebagai berikut:

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

Proyek kosong dengan pengaturan berikut:

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

Hasil

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

Diuji dengan kode ini:

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);
        }
    }
}

5

Compiler melakukan banyak optimasi. Salah satunya adalah inlining, apakah programmer menginginkan atau tidak. Misalnya, MethodImplOptions tidak memiliki opsi "inline". Karena inlining secara otomatis dilakukan oleh kompiler jika diperlukan.

Banyak optimasi lainnya terutama dilakukan jika diaktifkan dari opsi build, atau mode "release" akan melakukan ini. Tetapi optimasi ini adalah semacam "berhasil untuk Anda, hebat! Tidak berhasil, biarkan saja" optimasi dan biasanya memberikan kinerja yang lebih baik.

[MethodImpl(MethodImplOptions.AggressiveInlining)]

hanyalah sebuah flag untuk kompiler yang benar-benar diinginkan oleh operasi inlining. Info lebih lanjut di sini dan di sini

Untuk menjawab pertanyaan Anda;

Tidak ada jaminan bahwa JIT akan memasukkannya sebaliknya. Apakah aku salah?

Benar. Tidak ada jaminan; Baik C # memiliki opsi "force inlining".

Mungkinkah melakukan hal semacam ini melukai kinerja / stabilitas / apa pun?

Dalam hal ini tidak, seperti yang dikatakan dalam Menulis Aplikasi Terkelola Kinerja Tinggi: A Primer

Metode mendapatkan dan mengatur properti pada umumnya adalah kandidat yang baik untuk inlining, karena semua yang mereka lakukan biasanya menginisialisasi anggota data pribadi.


1
Diharapkan jawaban sepenuhnya menjawab pertanyaan. Meskipun ini adalah awal dari sebuah jawaban, itu benar-benar tidak masuk ke kedalaman yang diharapkan untuk sebuah jawaban.

1
Memperbarui jawaban saya. Semoga ini bisa membantu.
myuce
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.