web-dev-qa-db-ja.com

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

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

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

    private SqlMetaData[] meta;

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

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

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

17
Serge

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

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

ためらいがある場合でも、両方を実行して、パフォーマンスの向上があるかどうかを比較できます。これが現在の唯一の方法です。しかし、私の賭けは、その違いはごくわずかであり、ソースコードは単に「うるさい」ものになるでしょう。

22
dagnelies

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

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

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

8
Eugene Podskal

編集:私は私の答えが質問に正確に答えていなかったことに気づきますが、実際のマイナス面はありませんが、私のタイミング結果からは実際のアップサイドもありません。インラインプロパティゲッターの違いは、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);
        }
    }
}
8
Chris Phillips

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

他の多くの最適化は、ビルドオプションから有効になっている場合に特に行われます。そうしないと、「リリース」モードでこれが行われます。しかし、これらの最適化は一種の「あなたのために機能しました、素晴らしい!.

[MethodImpl(MethodImplOptions.AggressiveInlining)]

ここではインライン化操作が本当に必要なコンパイラのフラグにすぎません。詳細情報 こちら および こちら

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

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

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

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

高性能マネージドアプリケーションの記述:入門

プロパティのgetメソッドとsetメソッドは、通常、インライン化に適した候補です。これは、通常、プライベートデータメンバーを初期化するだけだからです。

5
myuce