优化案例陈述

时间:2017-03-08 14:29:03

标签: c optimization

我们有一种将命令数据写入设备的方法。该方法首先将数据转换为设备接受的格式,然后将数据写入串行端口。数据转换使用下面给出的case语句完成。对于10个命令,我们需要转换数据。对于其他命令,我们不必转换数据(大约10个命令)。

客户投诉代码未经优化。经常使用一些不需要数据转换的命令。

是否会引用if else语句优化代码?
在这种情况下,还有其他选项来优化代码吗?

switch (cmd_no)
{
case CMD_WR_ACC:
    converted_command_data = (INT32)((((DOUBLE)cmd_data * CMD_WR_ACC_PARA1) / CMD_WR_ACC_PARA2) + 0.5);
    break;
case CMD_WR_BIAS:
    converted_command_data = (INT32)((((DOUBLE)cmd_data * CMD_WR_BIAS_PARA1) / CMD_WR_BIAS_PARA2) + 0.5);
    break;
case CMD_WR_SUP:
    converted_command_data = (INT32)((((DOUBLE)cmd_data * CMD_WR_SUP_PARA1) / CMD_WR_SUP_PARA2) + 0.5);
    break;
case CMD_WR_FIL:
    converted_command_data = (INT32)((((DOUBLE)cmd_data * CMD_WR_FIL_PARA1) / CMD_WR_FIL_PARA2) + 0.5);
    break;
    .
    .
default:
    converted_command_data = cmd_data;
    break;
}

5 个答案:

答案 0 :(得分:5)

如果没有优化"你的客户意味着重复,看起来你可以将大量的逻辑放在表格中。

假设您的命令类型按顺序编号,即

#define CMD_WR_ACC 0
#define CMD_WR_BIAS 1
#define CMD_WR_SUP 2
...

您可以像这样定义表格:

struct params {
    int convert;       // whether or not to convert
    double param1;
    double param2;
} params_table[] = {
    { 1, CMD_WR_ACC_PARA1, CMD_WR_ACC_PARA2 },
    { 1, CMD_WR_BIAS_PARA1, CMD_WR_BIAS_PARA2 },
    { 1, CMD_WR_SUP_PARA1, CMD_WR_SUP_PARA2 },
    ...
    { 0, 0, 0}
    ...
};

然后您的代码如下所示:

if (params_table[cmd_no].convert) {
    converted_command_data = (INT32)((((DOUBLE)cmd_data * 
         params_table[cmd_no].param1) / params_table[cmd_no].param2) + 0.5);
} else {
    converted_command_data = cmd_data;
}

如果命令的起始索引不为0,则您需要从cmd_no中减去最低的命令编号,以使索引进入表中。

答案 1 :(得分:3)

  

将if if语句直接优化代码吗?

不,if - else if列表与在机器代码级别使用switch完全相同,因为if - else if具有性质

if(integer == 1) 
{ ... }  
else if (integer == 2) 
{ ... }

其中1和2是任何类型的编译时整数常量。在这种情况下,它将产生100%等效的机器代码

switch(integer)
{
  case 1: ... break;
  case 2: ... break;
}
  

在这种情况下,还有其他选项来优化代码吗?

一些事情:

  • 如果开关使用的常数相邻,最好从0到 n ,整个开关可以用函数指针跳转表替换。现代编译器应该做到这一点,但是较旧的编译器可能会很困难。
  • 由于您将结果转换为int,因此在此处需要浮点时并不明显。用整数计算代替浮点计算可能会提高性能,特别是在缺少FPU的微控制器系统等方面。如果浮点数的唯一目的是舍入除法,那么考虑

    // probably slow
    int32_t a = (int32_t)((((double)cmd_data * CMD_WR_ACC_PARA1) / CMD_WR_ACC_PARA2) + 0.5);
    

    相当于

    // probably faster
    int32_t b = ( (cmd_data * CMD_WR_ACC_PARA1) + CMD_WR_ACC_PARA2/2 ) / CMD_WR_ACC_PARA2;
    

    (见Rounding integer division (instead of truncating)

答案 2 :(得分:1)

这甚至不需要case语句,因为它易于矢量化。最简单的方法之一是使用X-macro。

由于没有提供任何值,我只使用顺序整数作为占位符

"optional": ["es7.classProperties"]

编辑: 实际上,如果经常调用代码,则switch语句可能更加优化,但是在紧密循环中不是这样,因为大多数编译器都可以生成跳转表。如果启用了正确的选项,则其他参数甚至可以编译为单个常量(例如-Ofast with gcc)。幸运的是,X-macro也可以处理你的case语句:

#define XMACRO(X,...) \
  /*Label, Parameter1, Parameter2,...*/ \
  X(ACC, 1.0, 2.0, __VA_ARGS__) \
  X(BIAS, 3.0, 4.0, __VA_ARGS__) \
  X(SUP, 5.0, 6.0, __VA_ARGS__) \
  X(FILL, 7,0, 8.0, __VA_ARGS__) \
  //more entries here

#define AS_ENUM(label,...) CMD_WR_##label,
enum commands { XMACRO(AS_ENUM) CMD_WR_COUNT };

#define AS_PREMULTIPLIED(label, p1, p2,...) ((p1)/(p2)),
const double convert_multipliers[CMD_WR_COUNT] = { XMACRO(AS_PREMULTIPLIED) };

int convert_data(int cmd_data, unsigned cmd_no){
return (cmd_no >= CMD_WR_COUNT) 
  ? cmd_data
  : (int) (cmd_data * convert_multipliers[cmd_no] + 0.5);
}

了解其性能是否已经过优化的唯一方法是测量,但X-macros将有助于消除重复代码,并允许您以易于阅读和修改的表格格式将数据保存在一起(并且至少看起来优化) 。当你必须在路上添加另一个参数时,你只需要添加一行而不是遍历每个函数。

答案 3 :(得分:0)

将代码从交换机重写为if / then / else不会有太大改善。首先要尝试的是使用设置为最高的优化选项进行编译。让编程器,gcc我假设,为您优化它。这就是低悬的果实。

这对此有何帮助? 默认情况下,GCC通过代码大小和调试简易性来平衡代码速度优化。默认情况下,生成的gcc很容易调试,因为代码流与源代码更紧密地匹配,并且存储了变量(这很慢)但是这使得使用gdb和断点更容易调试,因为变量很容易在堆栈上可见。

当我们启用优化时,会进行更多优化,包括仅将一些局部变量存储在处理器寄存器(速度很快)中,并通过重新排序代码来优化通过cpu执行管道的代码流。缺点是单步执行机器代码时更难以遵循。编译也需要稍长的时间。 但值得!

要将优化标志设置为最高,请尝试-O3,以便为代码大小和执行时间设置更多优化:

-O3

这里有关于优化标志的很好的讨论: https://stackoverflow.com/a/32941238/6693299

使用优化标志集进行编译后,查看使用昂贵的浮点运算的数学运算。你需要它们吗?双乘法和除法是昂贵的,只是将它们转换为int32s?你确定需要这样做吗?你可以将数学转换为仅使用整数运算吗?

您可以分享列出的常量的值吗?您可以将部分或全部数学转换为查找表。

答案 4 :(得分:0)

您可以将公共代码重新组合在一起,并在编译或初始化时执行除法,而不是执行。

我假设CMD_WR_ *是增量(枚举的元素)。

首先在这里进行分组:

SELECT onsite.[WorkOrder],
       DATEDIFF(MINUTES, onsite.OnSiteSubmitted, offsite.OnSiteSubmitted)
FROM   [DBName].[dbo].[sw_onsite_offsite] onsite join [DBName].[dbo].[sw_onsite_offsite] offsite on onsite.WorkOrder = offsite.WorkOrder
WHERE  onsite.Name = 'On-Site' AND
       offsite.Name = 'Off-Site'
ORDER BY WorkOrder

然后用if / else(如果有的话)进行计算:

double coef[] = {CMD_WR_ACC_PARA1/CMD_WR_ACC_PARA2, CMD_WR_BIAS_PARA1/CMD_WR_BIAS_PARA2, CMD_WR_SUP_PARA1/CMD_WR_SUP_PARA2, CMD_WR_FIL_PARA1/CMD_WR_FIL_PARA2, etc};

如果它们很多,可以使用开关:

if (cmd_no >= CMD_WR_ACC && cmd_no <= CMD_WR_FIL) {
   converted_command_data = (INT32)((DOUBLE)cmd_data * coef[cmd_no-CMD_WR_ACC]) + 0.5;
} else {
    converted_command_data = cmd_data;
}

(我不认为这会在性能方面产生重大影响)