在简单属性上使用AggressiveInlining有不利之处吗?


16

我敢打赌,如果我对分析C#/ JIT行为的工具有更多了解,我会自己回答,但是由于我不了解,请耐心询问。

我有这样的简单代码:

    private SqlMetaData[] meta;

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

如您所见,我放入了AggressiveInlining,因为我认为应该对其进行内联。
我认为。不能保证JIT可以内联它。我错了吗?

做这种事情会损害性能/稳定性/任何东西吗?


2
1)根据我的经验,此类原始方法将内联而没有属性。我主要发现该属性对于仍应内联的非平凡方法很有用。2)也不能保证用该属性修饰的方法也可以内联。这只是对JITter的提示。
CodesInChaos

我对新的内联属性了解不多,但是几乎可以肯定不会对性能产生任何影响。您要做的就是返回对数组的引用,并且JIT几乎可以肯定已经在这里做出了正确的选择。
罗伯特·哈维

14
3)内联过多意味着代码变大,可能不再适合缓存。缓存未命中可能会严重影响性能。4)我建议在基准测试表明它可以提高性能之前,不要使用该属性。
CodesInChaos 2014年

4
别担心 您尝试越胜于编译器的机会越多,它将找到更多使您胜过您的方法。寻找其他需要担心的事情。
david.pfx

1
对于我的两分钱,我看到了释放模式的巨大收获,特别是在紧密循环中调用较大的函数时。
jjxtra

Answers:


22

编译器是聪明的野兽。通常,他们会自动从任何地方挤出尽可能多的性能。

试图使编译器的性能正常,通常不会带来太大的变化,并且有很大的机会适得其反。例如,内联使您的程序更大,因为它可以在各处复制代码。如果在整个代码中的很多地方都使用了函数,则@CodesInChaos指出,它实际上可能是有害的。如果显然应该内联函数,则可以确信编译器会这样做。

在犹豫的情况下,您仍然可以同时进行两个操作,并比较性能是否有所提高,这是目前唯一的确定方法。但是我敢打赌,区别将是微不足道的,源代码只会变得“嘈杂”。


3
我认为“噪音”是这里最重要的一点。除非另有证明,否则请保持代码整洁并信任编译器执行正确的操作。其他所有事情都是危险的过早优化。
5gon12eder

1
如果编译器是如此聪明,那么为什么要设法使编译器适得其反呢?
Little Endian

11
编译器并不聪明。编译器不执行“正确的事情”。不要将智力归于非智力。实际上,C#编译器/ JITer太笨了。例如,它不会内联超过32字节IL的任何内容,也不会内联涉及structs作为参数的情况-在很多情况下它应该并且可以。除了缺少数百个明显的优化(包括但不限于)之外,还要避免不必要的边界检查和分配。
JBeurer '16

4
C#中的@DaveBlack Bounds检查难点发生在非常基本的情况的极少数列表中,通常发生在最基本的后续for循环上,即使如此,许多简单的循环也未能优化。多维数组循环不会消除边界检查,降序迭代的循环不会,新分配数组的循环不会。在很多简单的情况下,您希望编译器能够完成任务。但事实并非如此。因为这什么,但很聪明。blogs.msdn.microsoft.com/clrcodegeneration/2009/08/13/...
JBeurer

3
编译器不是“聪明的野兽”。他们只是简单地应用了一系列启发式方法,并进行了权衡,以尝试为编译器作者所预期的大多数情况找到平衡。我建议阅读:docs.microsoft.com/zh-cn/previous-versions/dotnet/articles/…–
cdiggins

8

您是对的-无法保证将内联该方法-MSDN MethodImplOptions枚举,SO MethodImplOptions.AggressiveInlining与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#都没有“强制内联”选项。

做这种事情会损害性能/稳定性/任何东西吗?

在这种情况下,如“ 编写高性能托管应用程序:入门”中所述

属性get和set方法通常是内联的良好候选者,因为它们所做的通常只是初始化私有数据成员。


1
期望答案能完全回答问题。虽然这是答案的开始,但实际上并没有深入到答案的预期范围。

1
更新了我的答案。希望它会有所帮助。
myuce
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.