代码之家  ›  专栏  ›  技术社区  ›  Jon Seigel

C编译器是否足够智能以优化此代码?

  •  26
  • Jon Seigel  · 技术社区  · 15 年前

    请忽略此问题中的代码可读性。

    在性能方面,是否应这样编写以下代码:

    int maxResults = criteria.MaxResults;
    
    if (maxResults > 0)
    {
        while (accounts.Count > maxResults)
            accounts.RemoveAt(maxResults);
    }

    或者像这样:

    if (criteria.MaxResults > 0)
    {
        while (accounts.Count > criteria.MaxResults)
            accounts.RemoveAt(criteria.MaxResults);
    }

    ?

    编辑: criteria 是一个 class MaxResults 是一个简单的整数属性(即, public int MaxResults { get { return _maxResults; } } .

    C编译器是否处理 最大结果 作为一个黑匣子,每次评估它?或者,是否足够聪明地发现我有3个对同一属性的调用,并且在调用之间不修改该属性?如果…怎么办 最大结果 是一块地吗?

    优化的一个法则是预计算,所以我本能地像第一个清单一样编写了这段代码,但是我很好奇这种事情是否是自动为我做的(同样,忽略代码的可读性)。

    (注:我不想听到“微观优化”的论据,这可能在我发布的具体案例中有效。我只想知道到底发生了什么,还是没有发生什么。)

    6 回复  |  直到 15 年前
        1
  •  59
  •   Eric Lippert    15 年前

    首先,实际回答性能问题的唯一方法是通过两种方法进行实际尝试,并在实际条件下测试结果。

    也就是说,其他的答案说“编译器”不做这种优化,因为属性可能有对错的副作用。这个问题的问题(除了一个基本问题,即如果不进行实际的尝试和测量结果就无法回答这个问题)是,“编译器”实际上是两个编译器:C编译器(编译为MSIL)和JIT编译器(编译IL为机器代码)。

    C编译器从来没有做过这种优化;如前所述,这样做需要编译器对被调用的代码进行对等访问,并验证它计算的结果在被调用代码的生命周期内不会改变。C编译器不会这样做。

    JIT编译器可能会。没有理由不能。所有的代码都在那里。内联属性getter是完全自由的,如果抖动确定内联属性getter返回一个可以缓存在寄存器中并重新使用的值,则可以自由执行此操作。(如果您不希望它这样做,因为该值可以在另一个线程上修改,那么您已经有了一个争用条件错误;在担心性能之前修复该错误。)

    是否有抖动 内联属性fetch,然后注册值,我不知道。我对这种抖动几乎一无所知。但如果合适的话,可以这样做。如果您对它是否这样做感到好奇,您可以(1)询问编写抖动的团队中的某个人,或者(2)在调试器中检查抖动的代码。

    最后,让我借此机会注意到计算结果一次,存储结果并重新使用它是 并不总是优化 . 这是一个非常复杂的问题。有各种各样的事情需要优化:

    • 执行时间

    • 可执行代码大小——这对可执行时间有很大影响,因为大代码的加载时间更长,增加了工作集的大小,对处理器缓存、RAM和页面文件造成压力。从长远来看,小而慢的代码常常是 更快 比启动时间和缓存位置等重要指标中的大型快速代码要快。

    • 寄存器分配——这对执行时间也有很大的影响,特别是在像x86这样有少量可用寄存器的体系结构中。为快速重用注册一个值意味着对于其他需要优化的操作可用的寄存器更少;也许优化这些操作将是一个净胜利。

    • 等等。它会很快变得非常复杂。

    简而言之,您不可能知道写代码来缓存结果而不是重新计算结果实际上是(1)更快,还是(2)更好。 更好的性能并不总是意味着更快地执行特定的例程。 更好的性能是确定哪些资源对用户很重要——执行时间、内存、工作集、启动时间等等——并针对这些内容进行优化。如果你没有(1)与你的客户交谈以了解他们关心什么,以及(2)实际衡量你的变化是否在所期望的方向上具有可测量的效果,你就无法做到这一点。

        2
  •  7
  •   Nick Craver    15 年前

    如果 MaxResults 是一个属性,那么不,它不会优化它,因为getter可能有复杂的逻辑,比如:

    private int _maxResults;
    public int MaxReuslts {
      get { return _maxResults++; }
      set { _maxResults = value; }
    }
    

    看看如果代码行中的行为会如何改变?

    如果没有逻辑……你写的任何一个方法都是好的,这是一个非常微小的区别,所有关于它的可读性。 给你(或你的团队) …你就是那个看着它的人。

        3
  •  6
  •   T.J. Crowder    15 年前

    您的两个代码示例只保证在单线程环境中有相同的结果,而.NET没有,如果 MaxResults 是字段(不是属性)。除非使用同步特性,否则编译器不能假定 criteria.MaxResults 在循环过程中不会改变。如果它是一个属性,就不能假定使用该属性没有副作用。

    埃里克·利珀特非常正确地指出,这很大程度上取决于你所说的“编译器”。C->IL编译器?还是IL->机器代码(JIT)编译器?他有权指出,JIT很可能能够优化属性getter,因为它包含所有信息(而C->IL编译器不一定)。它不会改变多线程的情况,但是它仍然是一个好点。

        4
  •  4
  •   Thorarin    15 年前

    每次都会被调用和评估。编译器无法确定方法(或getter)是否具有确定性和纯粹性(没有副作用)。

    请注意,JIT编译器可以内联对属性的实际评估,使其有效地与简单字段一样快。

    使财产评估成为一种廉价的操作是很好的实践。如果在getter中进行一些繁重的计算,请考虑手动缓存结果,或者将其更改为方法。

        5
  •  2
  •   John Nicholas    15 年前

    为什么不测试它?

    只需设置两个控制台应用程序,就可以让它看起来1000万次,并比较结果…请记住,要将它们作为已正确安装的已正确发布的应用程序运行,否则您不能保证不只是运行msil。

    实际上,你可能会得到5个答案,说“你不应该担心优化”。他们显然不写程序,需要尽可能快的阅读前(如游戏)。

    如果这段代码是执行了数十亿次的循环的一部分,那么这种优化是值得的。例如,max results可以是一个重写的方法,因此您可能需要讨论虚拟方法调用。

    真的 只有 回答这些问题的方法是找出这是一段从优化中受益的代码。然后你需要知道那些增加执行时间的事情。实际上,我们只是凡人不能先验地做到这一点,因此必须简单地尝试2-3个不同版本的代码,然后进行测试。

        6
  •  0
  •   herzmeister    15 年前

    如果 criteria 是一个类类型,我怀疑它是否会被优化,因为同时另一个线程总是可以更改该值。为了 struct 我不确定,但我的直觉是它不会被优化,但我认为在这种情况下,它在性能上不会有太大的差别。