JIT编译器是否会优化(内联)不必要的变量声明?

8

我已经阅读了几篇关于内联函数调用的文章以及问题/答案,结论是最佳做法是让JIT编译器进行所有优化。这很有道理。

那么内联变量声明呢?编译器是否也会进行优化?

也就是说,像这样的代码:

        Dim h = (a + b + c) / 2       'Half-Perimeter

        If maxEdgeLength / (Math.Sqrt(h * (h - a) * (h - b) * (h - c)) / h) <= MaximumTriangleAspectRatio Then
           'Do stuff here.
        End If

比这个性能更好:
        Dim perimeter = a + b + c   'Perimeter
        Dim h = perimeter / 2       'Half-Perimeter

        Dim area = Math.Sqrt(h * (h - a) * (h - b) * (h - c)) 'Heron's forumula.
        Dim inradius = area / h
        Dim aspectRatio = maxEdgeLength / inradius

        If aspectRatio <= MaximumTriangleAspectRatio Then
            'Do stuff here.
        End If

当然,我更喜欢后者,因为它更易于阅读和调试,但如果存在性能下降,我无法承受。
注意:我已经确定了此代码作为瓶颈 - 不需要有关过早优化的反驳。 :-)

7
你承受不起多用 20 字节的内存吗?这很可能不会影响你应用的性能,尤其是如果这段代码被重复运行,每次增加的内存也只是那同样的 20 字节。 - Joel Coehoorn
编译器(而非JIT)难道不会在将代码翻译成MSIL的过程中对此类优化负责吗?(如果是这样,那么MS C#编译器又会进行多大程度的优化呢?)无论如何,在一个相对环境中进行基准测试,因为只有这样才能确切知道哪种方式更快,以及到什么程度。 - user166390
4
你是这里唯一能回答问题的人。你已经编写了两种代码。运行这两种代码并测量时间,然后你就会知道哪个更快。抖动是否存在无关紧要;知道抖动的作用并不能回答“哪个更快?”的问题。 - Eric Lippert
@JoelCoehoorn 看起来JRS并不关心内存消耗,而是关心代码运行速度。 - Evgeniy Berezovsky
@user166390 我尝试了有/没有本地变量声明的情况,当进行发布构建时,生成的IL代码中反映出了差异。因此,看起来确实是JIT的工作,通过注册本地变量,我猜测,但没有深入挖掘。 - Evgeniy Berezovsky
2个回答

17

临时变量是否具有名称并不重要。

但是您可以显着优化这种不平等。

您的代码如下:

If maxEdgeLength / (Math.Sqrt(h * (h - a) * (h - b) * (h - c)) / h) <= MaximumTriangleAspectRatio Then

将两边都乘以平方根,消去除法(因为平方根不能返回负数,所以不会改变不等式的关系):

If maxEdgeLength <= (Math.Sqrt(h * (h - a) * (h - b) * (h - c)) / h) * MaximumTriangleAspectRatio Then

现在,将两边平方以消除那个昂贵的平方根:

If maxEdgeLength * maxEdgeLength <= h * (h - a) * (h - b) * (h - c) / h / h * MaximumTriangleAspectRatio * MaximumTriangleAspectRatio Then

取消并乘以h

If maxEdgeLength * maxEdgeLength * h <= (h - a) * (h - b) * (h - c) * MaximumTriangleAspectRatio * MaximumTriangleAspectRatio Then

这样会更快。如果这个计算需要重复使用,考虑缓存部分表达式的结果以获得更多的性能提升。

使用注释来解释公式。在一个性能瓶颈函数中消除Math.Sqrt的调用,值得用不太简单的格式编写此表达式。


1
不要只是使用注释,将计算重构为一个独立的函数——这段代码太长了。根据需要解释那个函数(特别是解释上面所做的转换)。 - Konrad Rudolph
6
感谢您指出优化并非在代码或编译中找到,而是在算法本身中找到的观点。+1 - Kiley Naro

5
顺便说一下,我想提出一个反对意见:JIT内联整个函数时会查看MSIL字节长度而不是计算复杂度。添加本地变量(并期望JIT将其注册)可能会增加函数的MSIL大小,从而使整个函数不适合内联。这不太可能像不必要地使用Math.Sqrt那样产生很大的差异,但这是有可能的。正如Eric Lippert所说,只有通过实际测量才能知道更多信息。然而,这种测量仅对程序的一个特定运行有效,并且不能推广到不同的处理器或未来版本的.NET运行时(包括服务包),这些版本通常会调整JIT的行为。因此,您需要综合分析和实证方法来进行优化。

1
你说得很好,Ben。除了一些琐碎的情况外,实际上并不存在所谓的“优化”。每一个所谓的“优化”都是一种权衡,我们希望它更好,但可能并不是。你在这个计算中使用更多的寄存器来换取那个计算中可用的寄存器更少,并希望你选择了需要更快的那个。或者你在使用更多的内存来换取更少的时间,并希望这样做不会导致后面的缓存未命中等问题。等等。 - Eric Lippert
@EricLippert:是的,绝对的优化涉及到权衡。但这与我所要表达的观点无关。当使用JIT编译时,您会受到更改的优化器算法的影响,这些算法在给定相同MSIL输入的情况下进行不同的权衡。即使是传统的AOT本地编译器,在具有不同CPI特征的不同处理器上执行相同的指令序列可能会产生不同的性能表现。 - Ben Voigt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接