単純なプロパティでAggressiveInliningを使用することには欠点がありますか?


16

C#/ JITがどのように動作するかを分析するためのツールについてもっと知っていれば、私は自分自身に答えることができるに違いないが、そうではないので、私に尋ねてください。

私はこのような簡単なコードを持っています:

    private SqlMetaData[] meta;

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

ご覧のとおり、AggressiveInliningをインライン化する必要があると感じたため、AggressiveInliningを配置しました。
おもう。それ以外の場合、JITがインライン化する保証はありません。私が間違っている?

この種のことを行うと、パフォーマンス/安定性/何かが損なわれる可能性がありますか?


2
1)私の経験では、このようなプリミティブメソッドは属性なしでインライン化されます。私は主に、まだインライン化されるべき自明でないメソッドでこの属性が役立つことを発見しました。2)属性で装飾されたメソッドがインライン化される保証もありません。これは、JITterへの単なるヒントです。
CodesInChaos

新しいインライン化属性についてはあまり知りませんが、ここに1つ追加してもパフォーマンスはほとんど変わりません。あなたがしているのは、配列への参照を返すことだけです。JITはほぼ確実にここで正しい選択をしています。
ロバートハーヴェイ14年

14
3)インライン化が多すぎると、コードが大きくなり、キャッシュに収まらなくなる可能性があります。キャッシュミスはパフォーマンスに重大な影響を与える可能性があります。4)パフォーマンスが向上することがベンチマークで示されるまで、属性を使用しないことをお勧めします。
CodesInChaos 14年

4
心配しないでください。コンパイラの裏をかこうとすればするほど、あなたを裏切る方法が見つかるでしょう。心配する他の何かを見つけてください。
david.pfx 2014年

1
私は2セントの間、特にタイトなループでより大きな関数を呼び出すときに、リリースモードで大きな利益を見てきました。
jjxtra

回答:


22

コンパイラは賢い獣です。通常、彼らはどこからでもできるだけ多くのパフォーマンスを自動的に絞り出します。

コンパイラの裏をかこうとすることは、通常大きな違いを生まないため、裏目に出る可能性が多くあります。たとえば、インライン化すると、コードがどこにでも複製されるため、プログラムが大きくなります。関数がコード全体の多くの場所で使用されている場合、@ CodesInChaosで指摘されているように、実際には有害である可能性があります。関数がインライン化されることが明らかな場合は、コンパイラーがインライン化することは間違いありません。

ためらいがある場合でも、両方を実行し、パフォーマンスの向上があるかどうかを比較できます。それが今のところ唯一の確実な方法です。しかし、私の違いは、違いは無視でき、ソースコードは「うるさい」だけだということです。


3
ここでは、「ノイズ」が最も重要なポイントだと思います。コードを整頓し、他の方法で証明されるまでコンパイラーが正しいことをすることを信頼してください。他のすべては危険な時期尚早な最適化です。
5gon12eder

1
コンパイラが非常に賢いのなら、なぜコンパイラの裏目に出ようとしないのでしょうか?
リトルエンディアン

11
コンパイラは賢くありません。コンパイラは「正しいこと」を行いません。知性がそうでない場合は、知性を帰属させないでください。実際、C#コンパイラ/ JITerは非常に馬鹿です。たとえば、32バイトILを超えるインライン化やstruct、パラメーターとしてsを含むケースはインライン化されません。数百もの明らかな最適化が欠落していることに加えて-を含むがそれに限定されない-不要な境界チェックや他の割り当てを回避します。
JBeurer

4
C#の@DaveBlack Bounds check elusionは、非常に基本的なケースの非常に小さなリストで発生します。通常、実行されるループの最も基本的なシーケンシャルであり、それでも多くの単純なループが最適化に失敗します。多次元配列のループは境界チェックの削除を取得しません、降順で反復されるループは削除しません、新しく割り当てられた配列のループは削除しません。コンパイラが仕事をすることを期待する非常に多くの単純なケース。しかし、そうではありません。それは何でもありますが、賢いからです。blogs.msdn.microsoft.com/clrcodegeneration/2009/08/13/...
JBeurer

3
コンパイラは「スマートビースト」ではありません。多数のヒューリスティックを適用し、トレードオフを行って、コンパイラライターが予想した大部分のシナリオのバランスを見つけようとします。:私は読んで示唆docs.microsoft.com/en-us/previous-versions/dotnet/articles/...を
cdiggins

8

あなたが正しい-メソッドがインライン化されることを保証する方法はありません-MSDN MethodImplOptions列挙、SO MethodImplOptions.AggressiveInlining vs TargetedPatchingOptOut

プログラマはコンパイラよりもインテリジェントですが、私たちはより高いレベルで作業し、最適化は一人の人間の仕事の成果です。ジッタは、実行中に何が起こっているかを確認します。設計者が入力した知識に従って、実行フローとコードの両方を分析できます。あなたのプログラムをよりよく知ることができますが、彼らはCLRをよく知っています。そして、彼の最適化で誰がより正しいでしょうか?確かにわかりません。

そのため、最適化をテストする必要があります。たとえそれが非常に単純であっても。また、環境が変化する可能性があり、最適化または最適化解除が予期しない結果になる可能性があることを考慮してください。


8

編集:私は私の答えが質問に正確に答えていなかったことに気づきました、本当の欠点はありませんが、私のタイミングの結果からも本当の欠点はありません。インラインプロパティゲッターの違いは、5億回の繰り返しで0.002秒です。また、テストケースは、構造体を使用しているため、100%正確ではない場合があります。これは、ジッターと構造体のインライン化に関するいくつかの注意事項があるためです。

いつものように、本当に知る唯一の方法はテストを書いてそれを理解することです。次の構成での私の結果は次のとおりです。

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

次の設定の空のプロジェクト:

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

結果

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

このコードでテスト済み:

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

コンパイラは多くの最適化を行います。プログラマが望んでいるかどうかにかかわらず、インライン化はその1つです。たとえば、MethodImplOptionsには「インライン」オプションはありません。インライン化は、必要に応じてコンパイラーによって自動的に行われるためです。

ビルドオプションから有効にした場合、他の多くの最適化が特に行われます。そうしないと、「リリース」モードでこれが行われます。しかし、これらの最適化は一種の「あなたのために働いた、すばらしい!働いていない、そのままにしておく」最適化であり、通常はパフォーマンスが向上します。

[MethodImpl(MethodImplOptions.AggressiveInlining)]

ここでは、インライン化操作が実際に必要であることを示すコンパイラーのフラグにすぎません。詳細は こちらこちら

あなたの質問に答えるために;

それ以外の場合、JITがそれをインライン化する保証はありません。私が間違っている?

本当です。保障はありません; どちらのC#にも「強制インライン化」オプションはありません。

この種のことを行うと、パフォーマンス/安定性/何かが損なわれる可能性がありますか?

この場合、「高性能マネージドアプリケーションの記述:入門書」で述べられているように、

通常、プロパティのgetおよびsetメソッドは、プライベートデータメンバを初期化するだけなので、インライン化に適しています。


1
回答が質問に完全に回答することが期待されます。これは答えの始まりですが、実際には答えに期待される深さにはなりません。

1
私の答えを更新しました。それが役立つことを願っています。
-myuce
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.