我的简单计算器没有开关盒的新想法

New idea for my simple calculator without switch-case

本文关键字:新想法 开关 简单 计算器 我的      更新时间:2023-10-16

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

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);}
};

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

但我仍然怀疑代码速度和性能。它的性能是否更快?

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

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

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

因此,从本质上讲,我只看到您的新方法的好处。

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

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

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

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

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


总结一下:

  1. 不优化
  2. 尚不优化(仅适用于专家)
虽然在某些情况下确实

需要优化,但您最好从宏观优化开始,并认识到什么是放缓的根源。如果你的程序无论如何都在等待I/O(我怀疑计算器的情况),那么没有人会知道你的程序是否回复得更快。同样,如果您的解析器/分词器是执行的瓶颈,优化将无济于事。

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

  1. 使用了"内联",所以函数代码被复制到你使用它的地方,那里没有增益。
  2. 指针确实优化了性能,但在这里几乎不明显,因为您使用的是小尺寸变量(不像使用 MB 内存的巨大照片)。
  3. 代码
  4. 不是那么可读,当然你可以很容易地理解这段代码,但把这个约定想象成数千行代码。

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