如果你有这样的代码(用java编写,但适用于任何类似的语言):
public static void main(String[] args) {
int total = 0;
for (int i = 0; i < 50; i++)
total += i * doStuff(i % 2); // multiplies i times doStuff(remainder of i / 2)
}
public static int doStuff(int i) {
// Lots of complicated calculations
}
你可以看到还有改进的余地。 doStuff(i % 2)
仅返回两个不同的值 - 一个用于偶数上的doStuff(0)
,另一个用于奇数上的doStuff(1)
。因此,每次通过说doStuff(i % 2)
重新计算这些值时,你会浪费大量的计算时间/功能。你可以像这样改进:
public static void main(String[] args) {
int total = 0;
boolean[] alreadyCalculated = new boolean[2];
int[] results = new int[2];
for (int i = 0; i < 50; i++) {
if (!alreadyCalculated[i % 2]) {
results[i % 2] = doStuff(i % 2);
alreadyCalculated[i % 2] = true;
}
total += i * results[i % 2];
}
}
现在它访问存储的值而不是每次重新计算。保持这样的数组可能看起来很愚蠢,但对于像i = 0, i < 500
那样循环的情况,以及每次重新检查i % 32
或类似事件,数组都是一种优雅的方法。 / p>
这种代码优化是否有一个术语?我想更多地了解它的不同形式和惯例,但我缺乏简洁的描述。
答案 0 :(得分:4)
是否有这种代码优化的术语?
是的,有:
在计算中, memoization 是一种优化技术,主要用于通过存储昂贵的函数调用的结果来加速计算机程序,并在再次出现相同的输入时返回缓存的结果。
答案 1 :(得分:1)
Common-subexpression-elimination(CSE)与此相关。这种情况是这种情况的组合,并从循环中提升循环不变计算。
我同意CBroe的说法,你可以称之为特定形式的缓存记忆,尤其是你用笨重的alreadyCalculated
数组实现它的方式。您可以优化它,因为您知道哪些调用将是新值,哪些将是重复。通常情况下,为了所有调用者的利益,您可以在被调用函数内部使用静态数组实现memoization。理想情况下,您可以使用标记值来标记尚未计算结果的条目,而不是为此维护单独的数组。或者对于稀疏的输入值集,只需使用散列(而不是例如具有2 ^ 32个条目的数组)。
您也可以避开主循环中的if
。
public class Optim
{
public static int doStuff(int i) { return (i+5) << 1; }
public static void main(String[] args)
{
int total = 0;
int results[] = new int[2];
// more interesting if we pretend the loop count isn't known to be > 1, so avoiding calling doStuff(1) for n=1 is useful.
// otherwise you'd just do int[] results = { doStuff(0), doStuff(1) };
int n = 50;
for (int i = 0 ; i < Math.min(n, 2) ; i++) {
results[i] = doStuff(i);
total += i * results[i];
}
for (int i = 2; i < n; i++) { // runs zero times if n < 2
total += i * results[i % 2];
}
System.out.print(total);
}
}
当然,在这种情况下,我们可以进一步优化。 sum(0..n) = n * (n+1) / 2
,因此我们可以使用它来根据doStuff(0)
(偶数项的总和)和doStuff(1)
(奇数项的总和)得到封闭形式(非循环)解决方案)。因此,我们每次只需要两个doStuff()
结果,避免任何需要记忆。