首页 > 解决方案 > C 函数优化

问题描述

我有一个看起来像这样的函数,常量a1-e8(双精度浮点数)在代码中,比如说硬编码或#defined。该函数接受 -1.0 到 1.0 范围内的双精度数,并且需要分成四等份,如图所示。

在汇编语言优化之前,我可以进行任何其他代码优化来提高运行时性能吗?我尝试制作一个 x2 来保存 x*x 并使 e 常数乘以 x2*x2 但它实际上降低了性能。我还尝试查看是否可以将 x 的副本转换为整数并使用 switch 语句,但这也会降低性能。

double operation(double x) {
    if (x <= -0.75 && x >= -1.0) {
        return a1 + b1*x + c1*x*x + d1*x*x*x + e1*x*x*x*x;
    }
    else if (x <= -0.5) {
        return a2 + b2*x - c2*x*x - d2*x*x*x - e2*x*x*x*x;
    }
    else if (x <= -0.25) {
        return a3 - b3*x - c3*x*x - d3*x*x*x - e3*x*x*x*x;
    }
    else if (x <= 0.0) {
        return a4 - b4*x - c4*x*x - d4*x*x*x + e4*x*x*x*x;
    }
    else if (x <= 0.25) {
        return a5 + b5*x - c5*x*x + d5*x*x*x + e5*x*x*x*x;
    }
    else if (x <= 0.5) {
        return a6 + b6*x - c6*x*x + d6*x*x*x - e6*x*x*x*x;
    }
    else if (x <= 0.75) {
        return a7 - b7*x - c7*x*x + d7*x*x*x - e7*x*x*x*x;
    }
    else if (x <= 1.0) {
        return a8 - b8*x + c8*x*x - d8*x*x*x + f8*x*x*x*x;
    }
    return 0.0;
}

标签: coptimization

解决方案


在汇编语言优化之前,我可以进行任何其他代码优化来提高运行时性能吗?

重新排列比较,以便您基本上是在对正确的情况进行二进制搜索而不是线性搜索,从而大大加快了速度:

double op2(double x) {
    if (x <= 0) {
        if (x <= -0.5) {
            if (x <= -0.75 && x >= -1.0) {
                return a1 + b1*x + c1*x*x + d1*x*x*x + e1*x*x*x*x;
            }
            return a2 + b2*x - c2*x*x - d2*x*x*x - e2*x*x*x*x;
        }
        else {
            if (x <= -0.25) {
                return a3 - b3*x - c3*x*x - d3*x*x*x - e3*x*x*x*x;
            }
            return a4 - b4*x - c4*x*x - d4*x*x*x + e4*x*x*x*x;
        }
    }
    else {
        if (x <= 0.5) {
            if (x <= 0.25) {
                return a5 + b5*x - c5*x*x + d5*x*x*x + e5*x*x*x*x;
            }
            return a6 + b6*x - c6*x*x + d6*x*x*x - e6*x*x*x*x;
        }
        else {
            if (x <= 0.75) {
                return a7 - b7*x - c7*x*x + d7*x*x*x - e7*x*x*x*x;
            }
            else if (x <= 1.0) {
                return a8 - b8*x + c8*x*x - d8*x*x*x + e8*x*x*x*x;
            }
        }
    }
    return 0.0;
}

我通过在同一个循环中调用原始版本op1op2两个函数返回相同的值。分析超过一亿次循环迭代的代码,我得到以下结果:

分析结果

因此,该op2版本的速度比原始版本快不到两倍。


更新:

我还测试了一个将输入映射到整数然后打开它的版本。这只有效,因为间隔都是相同的大小,所以虽然方法op2可以适用于任意间隔,但这个方法不会。为了进行映射,我将输入加 1,将输入范围移动到 [0, 2.0],然后乘以 4,将范围扩大到 [0, 8.0]。然后我将其转换为int以便我们可以打开它。switch具有多个连续值的语句的好处是编译器可以将其实现为跳转表,这使得它非常快。代价是额外的浮点乘法。这是功能:

double op3(double x) {
    int c = (int)((x + 1) * 4);    // mapping from double to int
    switch (c) {
        case 0: {
            return a1 + b1*x + c1*x*x + d1*x*x*x + e1*x*x*x*x;
        }
        case 1: {
            return a2 + b2*x - c2*x*x - d2*x*x*x - e2*x*x*x*x;
        }
        case 2: {
            return a3 - b3*x - c3*x*x - d3*x*x*x - e3*x*x*x*x;
        }
        case 3: {
            return a4 - b4*x - c4*x*x - d4*x*x*x + e4*x*x*x*x;
        }
        case 4: {
            return a5 + b5*x - c5*x*x + d5*x*x*x + e5*x*x*x*x;
        }
        case 5: {
            return a6 + b6*x - c6*x*x + d6*x*x*x - e6*x*x*x*x;
        }
        case 6: {
            return a7 - b7*x - c7*x*x + d7*x*x*x - e7*x*x*x*x;
        }
        case 7: {
            return a8 - b8*x + c8*x*x - d8*x*x*x + e8*x*x*x*x;
         }
        default: {
            return 0.0;
        }
    }
}

结果:

结果包括切换版本

所以,op3比原来的要快很多op1,但op2在这种情况下仍然是赢家。但是,如果您有更多案例,我认为您最终会达到将输入映射到整数的成本小于比较的成本的地步op2

查看这三个函数,您可以看到该op1方法的复杂度为 O(n),其中 n 是区间数。该op2方法是 O(log n),因为 n 个间隔需要 log n 个级别的比较。方法是op3O(1):一旦将输入映射到一个区间,switch 语句就可以使用跳转表在恒定时间内找到正确的情况。


推荐阅读