【问题标题】:Are there compilers capable of suggesting optimizations that would absolutely require programmer approval?是否有编译器能够建议绝对需要程序员批准的优化?
【发布时间】:2012-01-10 23:21:08
【问题描述】:

如果我们让人类参与循环,编译器能否做的不仅仅是严格的语义等价优化?

有一些潜在的优化被编译器直接忽略了,因为它们可能在语义上不等效。

但是,它们可能也很好,那么为什么不尝试检测并建议它们呢?检测可能涉及两个阶段的过程:编译时分析阶段和运行时分析阶段。

错误、警告和...建议?

编译器已经对“警告”进行了类似的处理,因为它们在每次编译期间都被汇集在一起​​,并且永远存在于列表中,直到您将它们解决到编译器满意为止。为什么不设置功能类似的“建议”或“建议的优化”部分,并有可能提高您的应用程序的性能?

如果编译器要分析布尔表达式的复杂性、估计的运行时间、单个操作数是真还是假的可能性等,那么它可以创建一个建议列表,例如表达式操作数的更好顺序,以及将建议作为列表呈现给程序员。然后,程序员可以单独处理它们,并决定忽略它们,或者让代码编辑器实施建议。

优化布尔表达式操作数顺序

考虑“短路逻辑表达式的优化”。因为操作数的顺序会影响哪个操作数可能被“短路”(即未调用),所以简单布尔表达式(即 A && B && C)中的操作数顺序是(我认为)编译器不会改变的东西避免在任何操作数有副作用时引入未知的副作用。

考虑一下:

char c = reader.ReadChar(); //Stream bs; const string NEWLINE;
while (!IsStringPresent( c, bs, NEWLINE ) && c != ',')

由于比较一个字符是(更快/不太复杂),它应该放在表达式的第一位,这样短路逻辑可以避免在遇到逗号时调用 IsStringPresent。同样,在这种情况下,逗号(每行很多)会比换行符更频繁地出现。

char c = reader.ReadChar(); //Stream bs; const string NEWLINE;
if (c != ',' && !IsStringPresent( c, bs, NEWLINE )) //faster for short-circuit; plus ',' is encountered more often than newline

总结

客观地,可以根据“A 与 B 为假以触发短路的频率”和“计算 A 与 B 的成本,有利于短路”来确定任何表达式“A && B”的最佳操作数顺序成本更高的那个”。如果编译器可以在编译时、运行时或两者中确定其中任何一个的近似值,那么它可以确定一个特定的表达式可能是次优的,并且它可以为程序员创建一个建议的更改来实现.

今天有编译器做这样的事情吗?如果不是,为什么?

【问题讨论】:

  • 对于您的短路示例,编译器的问题是 IsStringPresent 可能有副作用,不这样做是不正确的。所以是的,编译器不得不问。它必须写下假设列表,并且您必须对其进行审查。一旦该列表变长,它将成为错误的来源:假设发生了变化,您在列表中没有注意到它,这是错误的,现在优化变坏了。如果您必须将答案写成一堆击键,为什么不自己编写对操作数重新排序的击键呢?
  • 关于一般的短路:编译器可以做的是根据它的最后一次编译来决定被调用的函数是否有副作用。然后它可以根据最后的编译结果和您建议的操作成本对操作数重新排序。
  • 首先,正是因为短路,所以要避免包括副作用。编译器通过避免更改操作数的顺序来保证安全。如果程序员像经常建议的那样安全行事,在我们的短路表达式中不包含隐藏的“必需的副作用”,那么以逻辑等效的方式重新排序操作数应该是安全的。如果是这样的话,我就是从那里来的......那么我们应该优化这个顺序(实际上必须由人类决定),我认为编译器可以帮助我们而不会让它变得非常复杂。
  • 如果编译器知道短路表达式中被调用的函数确实没有副作用,则不需要huerstics。它可以以任何顺序评估操作数,因此它应该首先选择最便宜的操作数。问题是,编译器是怎么知道的? a)您断言(可能是错误的),b)编译器检查被调用的函数,c)您声明该函数是无副作用的(FORTRAN“纯”)并且编译器强制执行它。
  • 编译器不会尝试检测副作用,这是人类的工作。它会考虑像 (A && B && C && D) 这样的表达式,查看哪些 OP 是最好的“短路器”(即最有可能是 False),并查看评估每个 OP 的成本(简单比较与昂贵的函数调用)在编译时的复杂性、堆栈大小等方面。如果表达式看起来不是最佳的,那么它可以分析实际的运行时评估成本,并最终提出最佳顺序。说“嘿,(B && D && A && C)可能是一个更快的订购!”

标签: compilation compiler-optimization compiler-theory logical-operators short-circuiting


【解决方案1】:

您要问的主要是程序员的任务。在某种程度上,有一些工具,如静态分析工具或重构工具,可以扩展来完成你想要的。

编译器的任务是在规则范围内发挥标准(尽可能)。对于某些(积极的)优化,编译器确实提供了特定的标志,可以传递这些标志来启用/禁用它们 例如矢量化、内联、程序间等——这些优化很难手动完成。

重新排序(在您提到的示例中)受到 C、C++ 标准的限制,因为它们可能会改变程序的语义。在操作没有副作用的情况下,可以对它们进行重新排序,编译器在指令调度期间执行此类操作(在 clang++ 和 g++ 中的 -O2 处),但我不确定有多少其他编译器这样做。

【讨论】:

    【解决方案2】:

    我的回答是“可能”,但“这是我们真正想做的吗?”

    也许您是在建议编译器检测到可能的同构,但它不确定是否是同构,因此它会询问程序员“这是同构吗”,但无论如何“如果是这样,如果我应用它,将它会导致更快的代码,你觉得呢?”

    然而,我的想法更像是这样的:“嘿,程序员,如果我们能做到这一点,我们会得到更快的代码,但是,语义不同!我们必须加强这个函数的前置条件以做这个优化。可以吗?”

    加强前置条件可能没问题,哎呀,程序员可能甚至没有写前置条件。如果程序员说“当然,没关系”而程序员错了,那么运气好的话,测试就会发现“前提条件失败”的问题。

    【讨论】:

      【解决方案3】:

      我的观点是,仅仅因为程序员认为没问题就允许优化器使函数调用短路,这是导致无法追踪的错误的秘诀。它会在每次编译期间询问吗?我不知道;听起来像这样的事情会有很多问题。

      【讨论】:

      • 哦,不。假设总是发生短路,例如在 C# 中。 “决定是否短路”我的意思是“一个特定的操作数”,通过决定在表达式中将它排在第一位还是第二位。第二个表达式将被短路,而第一个表达式总是被计算。
      • 它当然会在每次编译期间检查潜在的优化,并将它们汇集在一个列表中,就像编译“警告”一样。如果程序员进行了建议的更改,那么在下一次编译期间显然不会被检测到。否则,它可能会像现在的警告一样永远坐在那里。
      • 是的,所以我是说......“短路表达式的优化”是通过将最快/最有可能的操作数放在第一位来决定操作数的最佳顺序触发后一个操作数的短路,并将运行速度较慢/更希望短路的操作数放在最后。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-04-01
      • 2011-01-05
      • 1970-01-01
      • 2021-03-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多