มีข้อเสียในการใช้ AggressiveInlining ในคุณสมบัติง่าย ๆ ?


16

ฉันพนันได้เลยว่าฉันจะตอบว่าตัวเองถ้าฉันรู้เพิ่มเติมเกี่ยวกับเครื่องมือในการวิเคราะห์พฤติกรรมของ C # / JIT แต่เนื่องจากฉันไม่ได้โปรดอดทนกับฉันถาม

ฉันมีรหัสง่าย ๆ เช่นนี้:

    private SqlMetaData[] meta;

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

อย่างที่คุณเห็นฉันใส่ AggressiveInlining เพราะฉันรู้สึกว่ามันควรจะถูกแทรก
ฉันคิด. ไม่มีการรับประกันว่า JIT จะเป็นแบบอินไลน์ ฉันผิดหรือเปล่า?

การทำสิ่งนี้อาจส่งผลกระทบต่อประสิทธิภาพ / ความเสถียร / อะไรหรือไม่?


2
1) ในประสบการณ์ของฉันวิธีดั้งเดิมดังกล่าวจะถูก inline โดยไม่มีแอตทริบิวต์ ฉันพบว่าคุณลักษณะส่วนใหญ่มีประโยชน์กับวิธีการที่ไม่น่าสนใจที่ควรมีการฝังไว้ 2) ไม่มีการรับประกันว่าวิธีการตกแต่งด้วยแอตทริบิวต์จะถูก inline อย่างใดอย่างหนึ่ง มันเป็นเพียงคำใบ้ถึง JITter
CodesInChaos

ฉันไม่รู้อะไรมากเกี่ยวกับคุณสมบัติอินไลน์ใหม่ แต่การวางไว้ที่นี่แทบจะไม่ทำให้ประสิทธิภาพแตกต่างกัน สิ่งที่คุณกำลังทำอยู่คือส่งคืนการอ้างอิงไปยังอาร์เรย์และ JIT จะทำตัวเลือกที่ถูกต้องที่นี่แล้ว
Robert Harvey

14
3) การทำอินไลน์มากเกินไปหมายความว่าโค้ดมีขนาดใหญ่ขึ้นและอาจไม่เหมาะกับแคชอีกต่อไป การพลาดแคชนั้นอาจส่งผลกระทบอย่างมากต่อประสิทธิภาพการทำงาน 4) ฉันแนะนำไม่ให้ใช้แอททริบิวต์จนกว่าเบนช์มาร์กจะแสดงว่ามันปรับปรุงประสิทธิภาพ
CodesInChaos

4
เลิกกังวล ยิ่งคุณพยายามที่จะฉลาดกว่าคอมไพเลอร์มากเท่าไหร่มันก็ยิ่งหาวิธีที่จะเอาชนะคุณได้มากเท่านั้น ค้นหาสิ่งอื่นที่ต้องกังวล
david.pfx

1
สำหรับสองเซ็นต์ของฉันฉันเห็นกำไรมากในโหมดการเปิดตัวโดยเฉพาะเมื่อเรียกฟังก์ชั่นที่มีขนาดใหญ่ขึ้นในวงที่แน่น
jjxtra

คำตอบ:


22

คอมไพเลอร์เป็นสัตว์ร้าย โดยปกติแล้วพวกเขาจะบีบการแสดงให้ได้มากที่สุดเท่าที่จะทำได้โดยอัตโนมัติ

การพยายามเอาชนะตัวแปลภาษามักจะไม่สร้างความแตกต่างใหญ่โตและมีโอกาสมากมายที่จะย้อนกลับ ตัวอย่างเช่นการทำอินไลน์ทำให้โปรแกรมของคุณใหญ่ขึ้นเนื่องจากมันจะทำซ้ำรหัสทุกที่ หากมีการใช้งานฟังก์ชันของคุณในหลาย ๆ ที่ตลอดทั้งรหัสมันอาจเป็นอันตรายอย่างยิ่งตามที่ระบุไว้ใน @CodesInChaos หากเห็นได้ชัดว่าควรมีฟังก์ชั่นอินไลน์คุณสามารถเดิมพันได้ว่าคอมไพเลอร์จะทำเช่นนั้น

ในกรณีที่ลังเลคุณยังสามารถทำทั้งสองอย่างและเปรียบเทียบว่ามีประสิทธิภาพเพิ่มขึ้นหรือไม่นั่นเป็นวิธีเดียวที่จะทำได้ในตอนนี้ แต่การเดิมพันของฉันคือความแตกต่างจะไม่อาจปฏิเสธได้ซอร์สโค้ดจะเป็น "noisier"


3
ฉันคิดว่า "เสียงรบกวน" เป็นจุดที่สำคัญที่สุดที่นี่ รักษาโค้ดของคุณให้เป็นระเบียบและไว้วางใจผู้แปลของคุณให้ทำสิ่งที่ถูกต้องจนกว่าจะพิสูจน์เป็นอย่างอื่น ทุกอย่างอื่นคือการเพิ่มประสิทธิภาพก่อนวัยอันควรที่เป็นอันตราย
5gon12eder

1
หากคอมไพเลอร์สมาร์ทดังนั้นทำไมจะพยายามเอาชนะการคอมไพเลอร์ backfire
Little Endian

11
คอมไพเลอร์ไม่ได้เป็นสมาร์ท คอมไพเลอร์ไม่ได้ทำ "สิ่งที่ถูกต้อง" อย่าใช้ความฉลาดทางฉลาด ในความเป็นจริง C # คอมไพเลอร์ / JITer เป็นใบ้มากเกินไป ตัวอย่างเช่นมันจะไม่อินไลน์อะไรเลยขนาด 32 ไบต์ IL หรือกรณีที่เกี่ยวข้องกับstructs เป็นพารามิเตอร์ - ซึ่งในหลาย ๆ กรณีควรและสามารถทำได้ นอกเหนือจากการเพิ่มประสิทธิภาพที่หายไปหลายร้อยอย่างชัดเจนซึ่งรวมถึง แต่ไม่ จำกัด เพียง - หลีกเลี่ยงการตรวจสอบและจัดสรรขอบเขตที่ไม่จำเป็นโดยไม่จำเป็น
JBeurer

4
@DaveBlack ขอบเขตการตรวจสอบ elusion ใน C # เกิดขึ้นในรายการเล็ก ๆ ของกรณีพื้นฐานมากโดยปกติแล้วจะเป็นลำดับขั้นพื้นฐานที่สุดสำหรับการวนซ้ำที่ทำได้ อาร์เรย์หลายมิติจะไม่ได้รับการตรวจสอบขอบเขตการวนซ้ำวนซ้ำในลำดับจากมากไปน้อยไม่ได้วนซ้ำในอาร์เรย์ที่จัดสรรใหม่ไม่ได้ กรณีง่าย ๆ มากมายที่คุณคาดหวังว่าคอมไพเลอร์จะทำงานได้ แต่มันก็ไม่ได้ เพราะมันเป็นอะไรก็ได้ แต่ฉลาด blogs.msdn.microsoft.com/clrcodegeneration/2009/08/13/…
JBeurer

3
คอมไพเลอร์ไม่ใช่ "สัตว์ร้าย" พวกเขาใช้ฮิวริสติกจำนวนมากและทำการแลกเปลี่ยนเพื่อลองและหาสมดุลสำหรับสถานการณ์ส่วนใหญ่ที่คาดการณ์โดยนักเขียนคอมไพเลอร์ ฉันขอแนะนำให้อ่าน: docs.microsoft.com/en-us/previous-versions/dotnet/articles/…
cdiggins

8

คุณมีสิทธิ - มีวิธีใดที่จะรับประกันว่าวิธีการที่จะได้รับการ inlined - MSDN MethodImplOptions การแจงนับดังนั้นMethodImplOptions.AggressiveInlining VS TargetedPatchingOptOut

โปรแกรมเมอร์มีความฉลาดกว่าคอมไพเลอร์ แต่เราทำงานในระดับที่สูงขึ้นและการเพิ่มประสิทธิภาพของเราเป็นผลงานของชายคนหนึ่ง - ของเราเอง กระวนกระวายใจเห็นสิ่งที่เกิดขึ้นในระหว่างการดำเนินการ มันสามารถวิเคราะห์ทั้งโฟลว์การประมวลผลและโค้ดตามความรู้ที่นักออกแบบวางไว้ คุณสามารถรู้จักโปรแกรมของคุณได้ดีขึ้น แต่พวกเขารู้ดีกว่า CLR และใครจะถูกต้องมากขึ้นในการเพิ่มประสิทธิภาพของเขา? เราไม่รู้แน่ชัด

นั่นเป็นเหตุผลที่คุณควรทดสอบการเพิ่มประสิทธิภาพใด ๆ ที่คุณทำ แม้ว่ามันจะง่ายมาก และคำนึงถึงสภาพแวดล้อมที่อาจมีการเปลี่ยนแปลงและการเพิ่มประสิทธิภาพหรือลดประสิทธิภาพของคุณอาจมีผลที่ไม่คาดคิด


8

แก้ไข: ฉันรู้ว่าคำตอบของฉันไม่ได้ตอบคำถามอย่างแน่นอนในขณะที่ไม่มีข้อเสียจริงจากผลการกำหนดเวลาของฉันก็ไม่มีส่วนต่างจริงเช่นกัน ความแตกต่างระหว่างตัวรับคุณสมบัติแบบอินไลน์คือ 0.002 วินาทีในการทำซ้ำ 500 ล้านครั้ง กรณีทดสอบของฉันอาจไม่ถูกต้อง 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 ไม่มีตัวเลือก "inline" เพราะการทำอินไลน์นั้นคอมไพเลอร์ทำได้โดยอัตโนมัติหากจำเป็น

การเพิ่มประสิทธิภาพอื่น ๆ อีกมากมายจะทำโดยเฉพาะถ้าเปิดใช้งานจากตัวเลือกการสร้างหรือโหมด "ปล่อย" จะทำ แต่การเพิ่มประสิทธิภาพเหล่านี้เป็น "ทำงานให้คุณดีเยี่ยมไม่ทำงานทิ้งไว้" การเพิ่มประสิทธิภาพและมักจะให้ประสิทธิภาพที่ดีกว่า

[MethodImpl(MethodImplOptions.AggressiveInlining)]

เป็นเพียงธงสำหรับคอมไพเลอร์ที่ต้องการการดำเนินการแบบอินไลน์ที่นี่จริงๆ ข้อมูลเพิ่มเติม ที่นี่และที่นี่

เพื่อตอบคำถามของคุณ

ไม่มีการรับประกันว่า JIT จะเป็นแบบอินไลน์ ฉันผิดหรือเปล่า?

จริง ไม่มีการรับประกัน; C # ไม่มีตัวเลือก "force inlining"

การทำสิ่งนี้อาจส่งผลกระทบต่อประสิทธิภาพ / ความเสถียร / อะไรหรือไม่?

ในกรณีนี้ไม่ใช่ดังที่กล่าวไว้ในการเขียนแอปพลิเคชันที่มีการจัดการประสิทธิภาพสูง: ไพรเมอร์

วิธีการรับและตั้งค่าคุณสมบัติมักเป็นตัวเลือกที่ดีสำหรับการทำอินไลน์เนื่องจากสิ่งที่พวกเขาทำคือการเริ่มต้นสมาชิกข้อมูลส่วนตัว


1
คาดว่าคำตอบจะตอบคำถามได้อย่างเต็มที่ ขณะนี้เป็นการเริ่มต้นของคำตอบจริง ๆ แล้วมันไม่ได้ไปลึกที่คาดไว้สำหรับคำตอบ

1
อัปเดตคำตอบของฉัน หวังว่ามันจะช่วย
myuce
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.