可以将此函数重构为O(1)

时间:2019-01-02 15:00:49

标签: performance function math refactoring

我有此函数,用于计算收益递减的值。它计算从输入值中减去一个不断增加的值的频率,并返回相减的次数。目前,它是通过无限循环迭代实现的:

// inputValue is our parameter. It is manipulated in the method body.
// step counts how many subtractions have been performed so far. It is also our returned value.
// loss is the value that is being subtracted from the inputValue at each step. It grows polynomially with each step.
public int calculate(int inputValue) {
    for (int step = 1; true; step++) {// infinite java for-each loop
        int loss = (int) (1 + 0.0006 * step*step + 0.2 * step);
        if (inputValue > loss) {
            inputValue -= loss;
        } else {
            return step;
        }
    }
}

此功能在大型应用程序的各个位置使用,有时在对性能要求较高的代码中使用。我希望将其重构为不再需要循环的方式。

我相当确定可以通过某种方式更直接地计算结果。但是我的数学技能似乎不足以做到这一点。

有人可以告诉我一个可以产生相同结果而无需循环或递归的函数吗?如果重构的代码对于极值和极端情况可能会产生不同的结果,则可以。负输入不需要考虑。

谢谢大家。

1 个答案:

答案 0 :(得分:2)

我认为您不可以使代码更快地保留确切的逻辑。特别是您很难在以下位置模拟舍入

   int loss = (int) (1 + 0.0006 * step*step + 0.2 * step);

如果这是您的业务逻辑要求而不是错误,那么我认为您不能做得更好。另一方面,如果您真正想要的是类似的东西(根据我假设您使用Java的语法):

public static int calculate_double(int inputValue) {
    double value = inputValue;
    for (int step = 1; true; step++) {// infinite java for-each loop
        double loss = (1 + 0.0006 * step * step + 0.2 * step); // no rounding!
        if (value > loss) {
            value -= loss;
        } else {
            return step;
        }
    }
}

即同样的逻辑,但每一步都没有四舍五入,那么就有一些希望。

注意:很遗憾,此舍入确实有所作为。例如,根据我的测试,calculate范围内的每个calculate_double的{​​{1}}和inputValue的输出都略有不同(有时甚至比{{1}大) },例如[4, 46465]+1 vs inputValue = 1000)。对于更大的calculate = 90,结果更加一致。例如,对于calculate_double = 88 / inputValue的结果,差异范围仅为519。对于每个结果,仍然会有一定范围的不同结果。

首先,在给定最大值520不取整的情况下([55294, 55547]),loss的总和具有一个封闭式:

step

因此从理论上找到这样的n可以使sum(n) = n + 0.0006*n*(n+1)*(2n+1)/6 + 0.2*n*(n+1)/2 通过求解具有closed formula的三次方程n并然后检查sum(n) < inputValue < sum(n+1)之类的值来完成和sum(x) = inputValue。但是,其背后的数学运算有点复杂,因此我没有走那条路。

还请注意,由于floor(x)的范围有限,因此从理论上讲,即使是该算法的实现也是ceil(x)(因为它永远不会比计算int花费更多的步骤,不变)。因此,您真正想要的只是大幅提高速度。

不幸的是,系数O(1)calculate(Integer.MAX_VALUE)很小,足以使不同的0.0006的总和的主要部分不同。仍然可以使用二进制搜索获得更好的性能:

0.2

注意n是原始逻辑的副本,它在减去最后一个static int sum(int step) { // n + 0.2 * n*(n+1)/2 + 0.0006 * n*(n+1)*(2n+1)/6 // ((0.0001*(2n+1) + 0.1) * (n+1) + 1) * n double s = ((0.0001 * (2 * step + 1) + 0.1) * (step + 1) + 1) * step; return (int) s; } static int calc_bin_search2(int inputValue) { int left = 0; // inputValue / 2 is a safe estimate, the answer for 100 is 27 or 28 int right = inputValue < 100 ? inputValue : inputValue / 2; // for big inputValue reduce right more aggressively before starting the binary search if (inputValue > 1000) { while (true) { int test = right / 8; int tv = sum(test); if (tv > inputValue) right = test; else { left = test; break; } } } // just usual binary search while (true) { int mid = (left + right) / 2; int mv = sum(mid); if (mv == inputValue) return mid; else if (mid == left) { return mid + 1; } else if (mv < inputValue) left = mid; else right = mid; } } 之后返回一个return mid + 1

在我的测试中,该实现与step的输出匹配,并且在loss下与calculate_double的性能大致相同,对于inputValue左右的值,速度提高了50倍,而x200 1000

附近的值更快