【问题标题】:Optimization of "static" loops“静态”循环的优化
【发布时间】:2012-05-01 03:43:05
【问题描述】:

我正在编写一种编译语言来取乐,而我最近开始努力使我的优化编译器变得非常健壮。我已经想出了几种优化某些东西的方法,例如,2 + 2 总是 4,所以我们可以在编译时进行数学运算, if(false){ ... } 可以完全删除等等,但是现在我已经开始循环了。经过一些研究,我认为我正在尝试做的并不是完全循环展开,但它仍然是一种优化技术。让我解释一下。

获取以下代码。

String s = "";
for(int i = 0; i < 5; i++){
    s += "x";
}
output(s);

作为一个人类,我可以坐在这里告诉你,这 100% 的时间都相当于

output("xxxxx");

所以,换句话说,这个循环可以完全“编译”出来。这不是循环展开,而是我所说的“完全静态”,也就是说,没有输入会改变段的行为。我的想法是,任何完全静态的东西都可以解析为单个值,任何依赖输入或产生条件输出的东西当然不能进一步优化。那么,从机器的角度来看,我需要考虑什么?是什么让循环“完全静态”?

我想出了三种类型的循环,我需要弄清楚如何分类。每次运行后总是以相同的机器状态结束的循环,无论输入如何,循环永远不会完成,以及我无法找出一种或另一种方式的循环。在我无法弄清楚的情况下(它有条件地根据动态输入改变它将运行的次数),我不担心优化。除非程序员明确禁止,否则无限循环将是编译错误/警告,并且每次都相同的循环应该直接跳过以使机器处于正确状态,而不是循环。

当然要优化的主要情况是静态循环迭代,此时内部的所有函数调用也是静态的。确定循环是否具有动态组件很容易,如果它不是动态的,我想它必须是静态的。我无法弄清楚的是如何检测它是否会是无限的。有人对此有任何想法吗?我知道这是停机问题的一个子集,但我觉得它是可以解决的;停止问题是一个问题,因为对于某些程序子集,您不能说它可能会永远运行,也可能不会,但我不想考虑这些情况,我只想考虑这些情况它会在哪里停止,或者它不会停止,但首先我必须区分这三种状态。

【问题讨论】:

  • 要了解目前这一行实际支持的内容,您可能需要阅读新 C++ 标准中对 constexpr 的限制。
  • 如果你可以静态判断循环条件始终为真,并且没有其他方法可以退出循环,那么你就知道循环不会终止。
  • 在您的示例中,您不一定知道 String s 也不会被另一个通过 extern 引用它并在并行线程中修改它的文件修改。
  • 来自维基百科的@JerryCoffin:“函数体不能声明变量”。这排除了大多数循环。我想知道这是由于理论限制还是实际限制。我觉得还有一部分问题仍然可以通过编程方式检测和解决,因此应该如此。
  • for(int i = 0; i &lt; 5; i--){} 在我看来并不是无限的,除非你有奇怪的不换行的整数。无论如何,检测“简单案例”很容易,那么您想走多远呢?

标签: optimization language-agnostic compiler-construction theory


【解决方案1】:

这看起来像是一种符号求解器,可以为多个类定义,但不是一般的。

让我们稍微限制一下要求:没有数字溢出,只有 for 循环(while 有时可以转换为完整的 for 循环,除非使用 continue 等),没有中断,没有修改 for 循环内的控制变量。

for (var i = S; E(i); i = U(i)) ...

其中 E(i) 和 U(i) 是可以符号操作的表达式。有几个类比较容易:

U(i) = i + CONSTANT:n-th循环i的值为S + n * CONSTANT

U(i) = i * CONSTANT : n-th 循环i 的值为S * CONSTANT^n

U(i) = i / CONSTANT : n-th 循环i 的值为S * CONSTANT^-n

U(i) = (i + CONSTANT) % M : n-th 循环i 的值为(S + n * CONSTANT) % M

和其他一些相当简单的组合(和一些非常困难的组合)

确定循环是否终止是搜索n,其中E(i(n)) 为假。 对于很多情况,这可以通过一些符号操作来完成,但是在制作求解器时需要做很多工作。

例如

  • for(int i = 0; i &lt; 5; i++),
  • i(n) = 0 + n * 1 = n, E(i(n)) => not(n &lt; 5) =>
  • n &gt;= 5 => 停在n = 5

  • for(int i = 0; i &lt; 5; i--),
  • i(n) = 0 + n * -1 = -n, E(i(n)) => not(-n &lt; 5) => -n &gt;= 5 =>
  • n &lt; -5 - 因为 n 是一个非负整数,所以这永远不会是真的 - 永远不会停止

  • for(int i = 0; i &lt; 5; i = (i + 1) % 3),
  • E(i(n)) => not(n % 3 &lt; 5) => n % 3 &gt;= 5 => 这绝不是真的 => 永远不会停止

  • for(int i = 10; i + 10 &lt; 500; i = i + 2 * i) =>
  • for(int i = 10; i &lt; 480; i = 3 * i),
  • i(n) = 10 * 3^n,
  • E(i(n)) => not(10 * 3^n &lt; 480) => 10 * 3^n &gt;= 480 => 3^n &gt;= 48 => n &gt;= log3(48) => n &gt;= 3.5... =>
  • 因为 n 是整数 => 它将停止 n = 4

对于其他情况,如果它们可以转换为您已经可以解决的情况会很好......

符号操作的许多技巧都来自 Lisp 时代,而且并不难。尽管所描述的(或变体)是最常见的类型实践,但还有许多更难和/或不可能解决的场景。

【讨论】:

  • 通常搞砸的是间接寻址(或索引到等效的数组)导致值之间可能出现别名。无论是否发生混叠,如果您不这样做,您就无法应用您的代数定律。因此,您需要非常好的流分析和别名解析来优化循环,除非它们像 OP 的示例那样对“整体”值进行操作。
  • 是的,前面应该提到过,这对于我们现在使用的大多数语言来说肯定是正确的。但是,由于这是@wraithguard01 正在制作的一种新语言,因此存在一些设计妥协和限制的开放领域,尽管我不确定现在会是什么。
  • 如果你有可变数组和索引,你就会遇到别名问题(将数组基数 + 索引视为指针,应该很明显)。
  • 因此,您在此处列出的所有案例似乎都可以转换为 (python) for + range (for i in range(10)) 构造。我可以很容易地看到如何优化它,我想知道这是否是关键?如果可以将其转换为 foreach(或者已经是带有静态输入的 foreach),则始终可以对其进行优化。
猜你喜欢
  • 1970-01-01
  • 2016-05-07
  • 2017-07-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多