간단한 속성에 AggressiveInlining을 사용하면 단점이 있습니까?


16

C # / JIT의 동작을 분석하는 도구에 대해 더 많이 알고 있다면 스스로 대답 할 수는 없지만 그렇게하지 않기 때문에 물어보십시오.

다음과 같은 간단한 코드가 있습니다.

    private SqlMetaData[] meta;

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

보시다시피 AggressiveInlining을 삽입해야한다고 생각하기 때문에 넣습니다.
내 생각 엔 JIT가 다른 방식으로 인라인 할 것이라는 보장은 없습니다. 내가 잘못?

이런 종류의 일을 수행하면 성능 / 안정성 / 모든 것이 손상 될 수 있습니까?


2
1) 내 경험상 그러한 원시적 인 방법은 속성없이 인라인 될 것입니다. 나는 주로 인라인되어야하는 사소하지 않은 메소드에 유용한 속성을 주로 발견했습니다. 2) 속성으로 꾸며진 메소드가 인라인 될 것이라는 보장은 없습니다. 그것은 단지 JITter에 대한 힌트 일뿐입니다.
코드 InChaos 2016 년

나는 새로운 인라이닝 속성에 대해 잘 모르지만 여기에 넣는 것이 성능에 거의 영향을 미치지 않을 것입니다. 당신이하고있는 일은 배열에 대한 참조를 반환하는 것입니다 .JIT는 거의 이미 여기에서 올바른 선택을 할 것입니다.
Robert Harvey

14
3) 너무 많이 인라인하면 코드가 커지고 더 이상 캐시에 맞지 않을 수 있습니다. 캐시 누락은 성능에 큰 타격을 줄 수 있습니다. 4) 벤치 마크에서 성능이 향상 될 때까지 속성을 사용하지 않는 것이 좋습니다.
코드 InChaos 2016 년

4
걱정마 컴파일러보다 현명한 선택을할수록 더 현명한 방법을 찾을 수 있습니다. 걱정할 다른 것을 찾으십시오.
david.pfx

1
내 두 센트의 경우, 특히 타이트한 루프에서 더 큰 함수를 호출 할 때 릴리스 모드에서 큰 이득을 보았습니다.
jjxtra

답변:


22

컴파일러는 똑똑한 짐승입니다. 일반적으로 가능한 모든 곳에서 가능한 한 많은 성능을 자동으로 짜냅니다.

컴파일러보다 현명한 시도는 큰 차이를 만들지 않으며 역효과를 일으킬 수있는 많은 기회가 있습니다. 예를 들어, 인라인은 코드가 모든 곳에서 복제되므로 프로그램이 더 커집니다. 함수가 코드 전체의 많은 곳에서 사용되면 실제로 @CodesInChaos가 지적한 것처럼 해로울 수 있습니다. 함수가 인라인되어야한다는 것이 명백하다면, 컴파일러가 그렇게 할 것입니다.

망설임의 경우에도 두 가지를 모두 수행하고 성능 향상이 있는지 비교할 수 있습니다. 이것이 유일한 방법입니다. 그러나 내기 내면의 차이는 무시할 수 없을 것이고, 소스 코드는 단지 "잡음"일 것이다.


3
여기서는 "잡음"이 가장 중요하다고 생각합니다. 코드를 깔끔하게 유지하고 그렇지 않으면 입증 될 때까지 올바른 작업을 수행하도록 컴파일러를 신뢰하십시오. 다른 모든 것은 위험한 조기 최적화입니다.
5gon12eder

1
컴파일러가 너무 똑똑하다면 왜 컴파일러의 역효과를 능가하려고할까요?
리틀 엔디안

11
컴파일러는 똑똑 하지 않습니다 . 컴파일러는 "옳은 일"을하지 않습니다. 지능이 아닌 곳에 지능을 부여하지 마십시오. 실제로 C # 컴파일러 / JITer는 너무 바보입니다. 예를 들어, 32 바이트 IL을 초과하는 항목이나 structs를 매개 변수로 사용 하는 경우는 인라인하지 않습니다 . 불필요한 경계 검사 및 다른 것들 사이의 할당을 피하는 것을 포함하여 수백 가지의 명백한 최적화 누락 - 이외에도 이에 국한되지는 않습니다.
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 Enumeration , 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

컴파일러는 많은 최적화를 수행합니다. 프로그래머가 원하든 원하지 않든 인라이닝이 그중 하나입니다. 예를 들어 MethodImplOptions에는 "인라인"옵션이 없습니다. 필요한 경우 컴파일러에서 인라인을 자동으로 수행하기 때문입니다.

다른 많은 최적화는 특히 빌드 옵션에서 활성화 된 경우 수행됩니다. 그러나 이러한 최적화는 일종의 "당신을 위해 일했습니다!

[MethodImpl(MethodImplOptions.AggressiveInlining)]

여기서는 인라인 작업이 실제로 필요하다는 컴파일러의 플래그 일뿐입니다. 여기여기에 더 많은 정보

귀하의 질문에 대답하기 위해;

JIT가 다른 방식으로 인라인 할 것이라는 보장은 없습니다. 내가 잘못?

진실. 보장하지 않습니다. C #에는 "강제 인라인"옵션이 없습니다.

이런 종류의 일을 수행하면 성능 / 안정성 / 모든 것이 손상 될 수 있습니까?

이 경우 아니요, 고성능 관리되는 응용 프로그램 작성 : A 입문서

속성 get 및 set 메소드는 일반적으로 개인 데이터 멤버를 초기화하기 때문에 인라인에 적합한 후보입니다.


1
답변이 질문에 완전히 대답 할 것으로 예상됩니다. 이것이 답변의 시작이지만 실제로는 답변에 필요한 수준으로 들어 가지 않습니다.

1
내 답변을 업데이트했습니다. 그것이 도움이되기를 바랍니다.
myuce
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.