0

我正在尝试实现第一个计算器。我的旧代码(开关盒):

enum arithmetic_type{
    add = 0,
    subtract = 1,
    multiply = 2,
    divide = 3
};

inline void calculate(double &var, double value, arithmetic_type type){

    switch(type)
    {
        case add : var += value;break;
        case subtract : var -= value;break;
        case multiply : var *= value;break;
        case divide : var /= value;break;
    }

}

我看到了“指向函数定义的指针”,然后有了一个新想法:改为使用单独的函数。现在我的代码看起来像

typedef void(*arithmetic_type)(double &var, double value); //template

inline void add(double &var, double value){var+=value;} //components
inline void subtract(double &var, double value){var-=value;}
inline void multiply(double &var, double value){var*=value;}
inline void divide(double &var, double value){var/=value;}
////////////////////////////////////////////////////////////////
struct VAR
{
    double var_value;
    arithmetic_type operator_type;
    inline void calculate(double value){operator_type(var_value, value);}
};

我发现它比 switch-case 简单得多。更重要的是,我将添加一些其他运算符,例如关系运算符......所以我认为这个新的解决方案比旧的 switch-case 解决方案更清晰,也更方便。:)

但我仍然怀疑代码速度和性能。它执行得更快吗?

4

3 回答 3

1

我个人更喜欢函数指针版本。它更直接地编码您想要的内容:一组操作,每个操作都是一个函数。

如果有的话,速度上的差异真的可以忽略不计。参数传递不应该有任何不同,因为指针和int大多数系统上的大小相同,并且您的枚举很可能使用与内部相同的大小存储int。如果有的话,取消引用指针并调用这些函数可能比执行切换更快,这很可能归结为一系列比较和条件跳转。

当然,有人可能会说您的操作非常简单,以至于将它们存储在一个函数中是多余的并且是不必要的高度抽象。我不同意,因为这些函数提供了您程序的基本核心,并且您希望它们都被尽可能纯粹和同质地处理。如果您需要使其中一个更复杂,它可能会使switch所有函数膨胀,但不会影响这种基于函数指针的设计。

所以,从本质上讲,我只看到你的新方法有好处。

于 2013-01-29T13:05:38.353 回答
0
  1. 您使用了“内联”,因此功能代码被复制到您使用它的位置,没有任何收获。
  2. 指针确实优化了性能,但在这里几乎不明显,因为您使用小尺寸的变量(不像会使用 MB 内存的巨大照片)。
  3. 代码不是那么可读,当然你可以很容易地理解这段代码,但是把这个约定想象成几千行代码。

所以我最后的想法是不要使用这样的指针,将它与大内存消耗变量一起使用。

于 2013-01-29T13:08:37.357 回答
0

首先 - 作为一般规则,你的资源是有限的。在许多情况下,您最宝贵的资源是编程时间。在这种情况下,您应该使用最简单/最易读的版本。从您的问题描述来看,您似乎已经开始过早地进行优化(除非它是优化中的玩具示例)。

也就是说,在某些情况下,您关心性能。通常,您有现有的实现,但性能不符合目标(例如 - 计算需要几天而不是几小时)。我怀疑“简单计算器”是否接近此类问题,但为了争论,我们假设它。然后,您可以使用整个宏观优化分支 - 即您应该更多地考虑大图(算法更改等)而不是小图(函数指针与案例)。首先,您需要找出导致减速的原因(如果某件事占用了程序运行 10% 的时间,并且您将其加速了 50%,那么总体改进将是 5%,另一方面,如果您改进程序的其余部分25%,性能将提高 22%)。

在更罕见的情况下,即使经过这样的优化,性能目标也足够高,以至于您的代码仍然不匹配。通常只有当您的程序计算量很大并且在许多机器上运行数月或数年时才会这样做 - 例如,蛋白质折叠值得以这种方式优化,而大多数程序,即使是流行的程序,都不值得。通常此时您需要知道:

  • 您正在优化的确切平台
  • 基本计算机架构(流水线、ILP、缓存架构、缓存一致性协议等)
  • 您正在使用的编译器

如果您在这种情况下,case/switch 可能会执行得更好,因为它允许在使用函数指针时更好地利用分支预测,而您只使用 BTB。另一方面,如果您的架构并且您使用的是非常简单的 C 编译器,则 case/switch 可能会溢出 I-cache(因此函数指针会执行得更好)。


总结一下:

  1. 不要优化
  2. 尚未优化(仅限专家)

虽然确实在某些情况下您通常需要优化,但最好从宏观优化开始并识别出减速的根源。如果您的程序无论如何都在等待 I/O(我怀疑使用计算器的情况),那么没有人会知道您的程序是否更快地响应 ns。同样,如果您的解析器/标记器是执行的瓶颈优化也无济于事。

如果宏观优化还不够,并且 1% 的运行时间改进可能值得您花费数周的时间,那么您可能需要查看了解处理器和编译器的微观优化。

于 2013-01-29T13:45:10.460 回答