Basit özelliklerde AggressiveInlining kullanmanın bir dezavantajı var mı?


16

Bahse girerim, C # / JIT'in nasıl davrandığını analiz etmek için araçlar hakkında daha fazla bilgi sahibi olsaydım kendime cevap verebilirdim, ancak yapmadığım için lütfen bana sor.

Bunun gibi basit bir kod var:

    private SqlMetaData[] meta;

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

Gördüğünüz gibi AggressiveInlining'i koydum çünkü satır içi olması gerektiğini hissediyorum.
Bence. JIT'in bunu başka bir şekilde satır içine alacağının garantisi yoktur. Yanlış mıyım?

Bu tür şeyleri yapmak performansa / istikrara / şeye zarar verebilir mi?


2
1) Benim tecrübelerime göre, bu tür ilkel yöntemler nitelik olmadan sıralı olacaktır. Özellikle öznitelik hala satır içi olması gereken önemsiz yöntemlerle yararlı buldum. 2) Özellikle dekore edilmiş bir yöntemin de satır içine alınacağının garantisi yoktur. Sadece JITter için bir ipucu.
CodesInChaos

Yeni satır içi niteliği hakkında çok şey bilmiyorum, ama buraya koymak neredeyse kesinlikle performansta herhangi bir fark yaratmayacak. Yaptığınız tek şey bir diziye bir referans döndürmek ve JIT neredeyse kesinlikle burada doğru seçimi yapacak.
Robert Harvey

14
3) Çok fazla satır içine alma, kodun büyüdüğü ve artık önbelleklere sığmayabileceği anlamına gelir. Önbellek isimleri önemli bir performans isabeti olabilir. 4) Bir kıyaslama performansı iyileştirdiğini gösterene kadar özelliği kullanmamanızı öneririm.
CodesInChaos

4
Endişelenmeyi bırak. Derleyiciyi ne kadar zekice kırmaya çalışırsanız, o kadar zekice sizi yoldan çıkarır. Endişelenecek başka bir şey bulun.
david.pfx

1
İki sentim için, özellikle dar bir döngüde daha büyük bir işlevi çağırırken, serbest bırakma modunda büyük kazançlar gördüm.
jjxtra

Yanıtlar:


22

Derleyiciler akıllı hayvanlardır. Genellikle, otomatik olarak ellerinden geldikleri her yerden mümkün olan en yüksek performansı alırlar.

Derleyiciyi alt etmeye çalışmak genellikle büyük bir fark yaratmaz ve geri tepme şansı vardır. Örneğin, satır içi ayar, kodu her yerde çoğaltırken programınızı büyütür. İşleviniz kod boyunca birçok yerde kullanılıyorsa, aslında @CodesInChaos'un işaret ettiği gibi zararlı olabilir. İşlevin satır içine alınması gerektiği açıksa, derleyicinin bunu yapacağına bahse girebilirsiniz.

Tereddüt durumunda, yine de her ikisini de yapabilir ve herhangi bir performans kazancı olup olmadığını karşılaştırabilirsiniz, şu an için tek kesin yol budur. Ama bahse girerim fark fark edilmeyecek, kaynak kodu sadece "daha gürültülü" olacak.


3
“Gürültü” nin en önemli nokta olduğunu düşünüyorum. Kodunuzu düzenli tutun ve aksi kanıtlanıncaya kadar doğru şeyi yapmak için derleyicinize güvenin. Diğer her şey tehlikeli bir erken optimizasyon.
5gon12eder

1
Derleyiciler bu kadar zekiyse, neden derleyicinin geri tepmesini atlatmaya çalışalım?
Little Endian

11
Derleyiciler akıllı değildir . Derleyiciler "doğru olanı" yapmazlar. İstihbaratı olmadığı yere atmayın. Aslında, C # derleyicisi / JITer aşırı derecede aptal. Örneğin, 32 bayt IL'den sonraki herhangi bir şeyi veya structparametreler olarak s içeren vakaları satır içi yapmaz - burada birçok durumda olması gerekir ve olabilir. Diğer şeylerin yanı sıra gereksiz sınır kontrolleri ve tahsislerinden kaçınmak da dahil, ancak bunlarla sınırlı olmamak üzere yüzlerce belirgin optimizasyonu kaçırmanın yanı sıra.
JBeurer

4
@DaveBlack Sınırları kontrol C # 'de elusyon, çok basit vakaların çok küçük bir listesinde gerçekleşir, genellikle gerçekleştirilen döngüler için en temel ardışıktır ve o zaman bile birçok basit döngü optimize edilemez. Çok boyutlu dizi döngüleri sınır denetimini almaz, azalan sırada yinelenen döngüler olmaz, yeni atanan dizilerdeki döngüler almaz. Derleyicinin işini yapmasını beklediğiniz çok basit durumlar. Ama öyle değil. Çünkü bu akıllıca bir şey değil. blogs.msdn.microsoft.com/clrcodegeneration/2009/08/13/…
JBeurer

3
Derleyiciler "akıllı hayvanlar" değildir. Sadece bir grup sezgisel tarama uygular ve derleyici yazarları tarafından beklenen senaryoların çoğunluğu için bir denge bulmaya çalışırlar. Okumanızı
cdiggins

8

Haklısın - yöntemin inline olacağını garanti etmenin bir yolu yok - MSDN MethodImplOptions Numaralandırma , SO MethodImplOptions.AggressiveInlining vs TargetedPatchingOptOut .

Programcılar bir derleyiciden daha zekidir, ancak daha yüksek bir seviyede çalışıyoruz ve optimizasyonlarımız bir erkeğin işinin ürünü - kendimiz. Jitter, infaz sırasında neler olduğunu görür. Hem yürütme akışını hem de kodu tasarımcıları tarafından içine konulan bilgilere göre analiz edebilir. Programınızı daha iyi tanıyabilirsiniz, ancak CLR'yi daha iyi bilirler. Ve optimizasyonlarında kim daha doğru olacak? Biz kesin olarak bilmiyoruz.

Bu yüzden yaptığınız optimizasyonu test etmelisiniz. Çok basit olsa bile. Ve ortamın değişebileceğini ve optimizasyonunuzun veya disoptinizasyonunuzun beklenmedik bir sonuç verebileceğini dikkate alın.


8

EDIT: Cevabım soruyu tam olarak cevap vermediğini, gerçek bir dezavantaj yok, zamanlama sonuçlarımdan da gerçek bir ters olmadığını anlıyorum. Satır içi mülkiyet alıcısı arasındaki fark 500 milyon yinelemede 0,002 saniyedir. Test durumum da% 100 doğru olmayabilir çünkü bir yapı kullanıyor çünkü titremede bazı uyarılar var ve yapılarla satır içi.

Her zaman olduğu gibi, gerçekten bilmenin tek yolu bir test yazmak ve anlamaktır. İşte aşağıdaki yapılandırmayla sonuçlarım:

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

Aşağıdaki ayarlarla boş proje:

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

Sonuçlar

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

Bu kodla test edildi:

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

Derleyiciler çok fazla optimizasyon yapar. Inlining, programcının istesin ya da istemesin bunlardan biridir. Örneğin, MethodImplOptions bir "satır içi" seçeneği yoktur. Çünkü eğer gerekirse satır içi otomatik olarak derleyici tarafından yapılır.

Diğer birçok optimizasyon, derleme seçeneklerinden etkinleştirilirse yapılır, aksi takdirde "serbest bırakma" modu bunu yapar. Ancak bu optimizasyonlar "sizin için çalıştı, harika! Çalışmadı, bırakın" optimizasyonlarıdır ve genellikle daha iyi performans verir.

[MethodImpl(MethodImplOptions.AggressiveInlining)]

sadece derleyici için burada bir satır içi işlemin gerçekten istendiği bir bayrak. Burada ve burada daha fazla bilgi

Soruna cevap vermek için;

JIT'in bunu başka bir şekilde satır içine alacağının garantisi yoktur. Yanlış mıyım?

Doğru. Garanti yok; Her iki C # için de "kuvvetli satır içi" seçeneği yoktur.

Bu tür şeyleri yapmak performansa / istikrara / şeye zarar verebilir mi?

Bu durumda hayır, Yüksek Performanslı Yönetilen Uygulamalar Yazarken belirtildiği gibi : Bir Astar

Mülk alma ve ayarlama yöntemleri genellikle satır içi işlem için iyi adaylardır, çünkü yaptıkları tek şey genellikle özel veri üyelerini başlatmaktır.


1
Cevapların soruyu tam olarak cevaplaması beklenir. Bu bir yanıtın başlangıcı olsa da, gerçekten bir cevap için beklenen derinliğe girmez.

1
Cevabım güncellendi. Umarım yardımcı olur.
myuce
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.