Có một nhược điểm nào khi sử dụng Ag xâmiveInlining trên các thuộc tính đơn giản không?


16

Tôi cá là tôi có thể tự trả lời nếu tôi biết nhiều hơn về các công cụ để phân tích cách C # / JIT hành xử nhưng vì tôi không làm ơn, xin hãy hỏi tôi.

Tôi có mã đơn giản như thế này:

    private SqlMetaData[] meta;

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

Như bạn có thể thấy tôi đặt Ag xâmiveInlining vì tôi cảm thấy như nó nên được nội tuyến.
Tôi nghĩ. Không có gì đảm bảo JIT sẽ nội tuyến nó khác. Tôi có lầm không?

Có thể làm điều này làm tổn thương hiệu suất / sự ổn định / bất cứ điều gì?


2
1) Theo kinh nghiệm của tôi, các phương thức nguyên thủy như vậy sẽ được nội tuyến mà không có thuộc tính. Tôi chủ yếu tìm thấy thuộc tính hữu ích với các phương thức không tầm thường vẫn nên được nội tuyến. 2) Không có gì đảm bảo rằng một phương thức được trang trí với thuộc tính cũng sẽ được nội tuyến. Nó chỉ là một gợi ý cho JITter.
CodeInChaos

Tôi không biết nhiều về thuộc tính nội tuyến mới, nhưng đặt một thuộc tính ở đây gần như chắc chắn sẽ không tạo ra bất kỳ sự khác biệt nào về hiệu suất. Tất cả những gì bạn đang làm là trả về một tham chiếu đến một mảng và JIT gần như chắc chắn sẽ đưa ra lựa chọn chính xác ở đây.
Robert Harvey

14
3) Nội tuyến quá nhiều có nghĩa là mã trở nên lớn hơn và có thể không phù hợp với bộ đệm nữa. Bộ nhớ cache có thể là một hit hiệu suất đáng kể. 4) Tôi khuyên bạn không nên sử dụng thuộc tính cho đến khi điểm chuẩn cho thấy rằng nó cải thiện hiệu suất.
CodeInChaos

4
Đừng lo lắng. Bạn càng cố gắng vượt qua trình biên dịch, nó sẽ càng tìm ra cách để vượt qua bạn. Tìm một cái gì đó khác để lo lắng.
david.pfx

1
Đối với hai xu của tôi, tôi đã thấy lợi nhuận lớn trong chế độ phát hành, đặc biệt là khi gọi một chức năng lớn hơn trong một vòng lặp chặt chẽ.
jjxtra

Câu trả lời:


22

Trình biên dịch là những con thú thông minh. Thông thường, họ sẽ tự động vắt kiệt hiệu suất nhất có thể từ bất cứ nơi nào họ có thể.

Cố gắng vượt qua trình biên dịch thường không tạo ra sự khác biệt lớn và có rất nhiều cơ hội để chống lại. Ví dụ, nội tuyến làm cho chương trình của bạn lớn hơn vì nó sao chép mã ở mọi nơi. Nếu chức năng của bạn được sử dụng ở nhiều nơi trong toàn bộ mã, thì nó thực sự có thể gây bất lợi như đã chỉ ra @CodesInChaos. Nếu rõ ràng là hàm nên được nội tuyến, bạn có thể đặt cược trình biên dịch sẽ làm như vậy.

Trong trường hợp do dự, bạn vẫn có thể thực hiện cả hai và so sánh nếu có bất kỳ hiệu suất nào, đó là cách duy nhất cho đến bây giờ. Nhưng đặt cược của tôi là sự khác biệt sẽ là không thể chấp nhận được, mã nguồn sẽ chỉ là "ồn ào".


3
Tôi nghĩ rằng tiếng ồn Tiếng Nhật là điểm quan trọng nhất ở đây. Giữ mã của bạn gọn gàng và tin tưởng trình biên dịch của bạn để làm điều đúng cho đến khi được chứng minh khác đi. Tất cả mọi thứ khác là một tối ưu hóa sớm nguy hiểm.
5gon12eder

1
Nếu trình biên dịch quá thông minh, vậy thì tại sao lại cố gắng vượt qua trình biên dịch ngược?
Little Endian

11
Trình biên dịch không thông minh . Trình biên dịch không làm "điều đúng". Đừng quy kết trí thông minh ở nơi không. Trong thực tế, trình biên dịch C # / JITer quá mức câm. Ví dụ, nó sẽ không nội tuyến bất cứ thứ gì qua 32 byte IL hoặc các trường hợp liên quan đến structs dưới dạng tham số - trong đó có rất nhiều trường hợp nên và có thể. Ngoài việc thiếu hàng trăm tối ưu hóa rõ ràng - bao gồm, nhưng không giới hạn - tránh các kiểm tra và phân bổ giới hạn không cần thiết trong số những thứ khác.
JBeurer

4
@DaveBlack Bound kiểm tra sự tách biệt trong C # xảy ra trong một danh sách rất nhỏ các trường hợp rất cơ bản, thường là theo trình tự cơ bản nhất cho các vòng lặp được thực hiện, và thậm chí sau đó nhiều vòng lặp đơn giản không được tối ưu hóa. Các vòng lặp đa chiều không được loại bỏ kiểm tra giới hạn, các vòng lặp được lặp theo thứ tự giảm dần không, các vòng lặp trên các mảng mới được phân bổ không. Rất nhiều trường hợp đơn giản mà bạn mong muốn trình biên dịch thực hiện công việc của mình. Nhưng nó không. Bởi vì đó là bất cứ điều gì, nhưng thông minh. blogs.msdn.microsoft.com/clrcodegeneration/2009/08/13/...
JBeurer

3
Trình biên dịch không phải là "con thú thông minh". Họ chỉ đơn giản là áp dụng một loạt các heuristic và thực hiện đánh đổi để thử và tìm sự cân bằng cho phần lớn các kịch bản được các nhà văn biên dịch dự đoán. Tôi đề nghị đọc: docs.microsoft.com/en-us/preingly-versions/dotnet/articles/ mẹo
cdiggins

8

Bạn đã đúng - không có cách nào để đảm bảo rằng phương thức này sẽ được nội tuyến - Phương pháp MSDNImplOptions Enumutions , SO MethodImplOptions.AggressiveInlining vs TargetedPatchingOptOut .

Các lập trình viên thông minh hơn một trình biên dịch, nhưng chúng tôi làm việc ở cấp độ cao hơn và tối ưu hóa của chúng tôi là sản phẩm của công việc của một người - của chính chúng tôi. Jitter thấy những gì đang diễn ra trong quá trình thực thi. Nó có thể phân tích cả luồng thực thi và mã theo kiến ​​thức được đưa vào bởi các nhà thiết kế của nó. Bạn có thể biết chương trình của bạn tốt hơn, nhưng họ biết rõ hơn về CLR. Và ai sẽ đúng hơn trong tối ưu hóa của mình? Chúng tôi không biết chắc chắn.

Đó là lý do tại sao bạn nên kiểm tra bất kỳ tối ưu hóa nào bạn thực hiện. Ngay cả khi nó rất đơn giản. Và hãy tính đến việc môi trường có thể thay đổi và việc tối ưu hóa hoặc không tối ưu hóa của bạn có thể có một kết quả khá bất ngờ.


8

EDIT: Tôi nhận ra câu trả lời của mình không trả lời chính xác câu hỏi, trong khi không có nhược điểm thực sự, từ kết quả thời gian của tôi cũng không có mặt trái thực sự. Sự khác biệt giữa một getter thuộc tính nội tuyến là 0,002 giây trên 500 triệu lần lặp. Trường hợp thử nghiệm của tôi cũng có thể không chính xác 100% vì nó sử dụng một cấu trúc bởi vì có một số cảnh báo cho jitter và nội tuyến với các cấu trúc.

Như mọi khi, cách duy nhất để thực sự biết là viết một bài kiểm tra và tìm ra nó. Đây là kết quả của tôi với cấu hình sau:

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

Dự án trống với các cài đặt sau:

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

Các kết quả

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

Đã thử nghiệm với mã này:

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

Trình biên dịch làm rất nhiều tối ưu hóa. Nội tuyến là một trong số đó, cho dù lập trình viên có muốn hay không. Ví dụ: MethodImplOptions không có tùy chọn "nội tuyến". Bởi vì nội tuyến được trình biên dịch tự động thực hiện nếu cần.

Nhiều tối ưu hóa khác được thực hiện đặc biệt nếu được bật từ các tùy chọn xây dựng hoặc chế độ "phát hành" sẽ làm điều này. Nhưng những tối ưu hóa này là loại "tối ưu cho bạn, tuyệt vời! Không hoạt động, hãy để nó" tối ưu hóa và thường cho hiệu suất tốt hơn.

[MethodImpl(MethodImplOptions.AggressiveInlining)]

chỉ là một lá cờ cho trình biên dịch mà một hoạt động nội tuyến thực sự muốn ở đây. Thêm thông tin ở đâyở đây

Để trả lời câu hỏi của bạn;

Không có gì đảm bảo JIT sẽ nội tuyến nó khác. Tôi có lầm không?

Thật. Không bảo đảm; Cả C # đều không có tùy chọn "bắt buộc nội tuyến".

Có thể làm điều này làm tổn thương hiệu suất / sự ổn định / bất cứ điều gì?

Trong trường hợp này là không, như đã nói trong phần Viết các ứng dụng được quản lý hiệu suất cao: Một Primer

Các phương thức lấy và đặt thuộc tính nói chung là các ứng cử viên tốt để đặt nội tuyến, vì tất cả những gì chúng làm thường là khởi tạo các thành viên dữ liệu riêng tư.


1
Dự kiến ​​câu trả lời sẽ trả lời đầy đủ câu hỏi. Mặc dù đây là một khởi đầu cho một câu trả lời, nhưng nó thực sự không đi sâu vào dự kiến ​​cho một câu trả lời.

1
Cập nhật câu trả lời của tôi. Hy vọng nó sẽ giúp.
myuce
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.