目的:用于获取算子的理论计算量。
实现思路:
(1)以编译器前端扫描的方式,识别各个语句的计算量。
(2)通过 theory_ops_ 变量记录理论计算量,逐语句(或块)来更新 theory_ops_。
(3)在运行时, theory_ops_ 会跟 cpuCompute 函数一起计算,theory_ops_ 的值作为参考的理论计算量。
用法:
(1)gtest 中 op.h 增加 theory_ops_ 变量,op.cpp 给需要统计计算量的函数(一般为 cpuCompute 和 调用的函数)
头尾增加 // count_ops_begin, // count_ops_end。
(2)在 main 文件中,输入 op.cpp 的路径,可以设置 show_theoryOps 的参数来输出结果。
(3)cpuCompute 函数中有些寻址语句或冗余写法不需要统计计算量,可以将其先注释掉。
yaml; ply (词法语法解析器); clang-format (格式对齐使用)
配置文件,记录了 Cpp 语言常用的关键字和运算符。
解析 base_config.yaml
词法解析规则
语法解析规则
parser_rules 运行生成的文件
扫描代码,解析代码,按时间输出带有 theory_ops_ 记录的代码
- 注释和[]之间的内容均忽略
- 函数调用的计算量均视为 1(待优化)
- 后置运算符 ++, -- 的计算量视为 3
- 后置运算符 ., -> 的计算量视为 0
- static_cast 等 cast 特殊运算符的计算量视为 1
- sizeof, typeId 的计算量视为 0
- 前置运算符 ++, -- 的计算量视为 2
- 前置运算符 -, !, ~ 修饰的是数字时计算量视为 0,其他为1
- 前置运算符 +, *, & 的计算量为 0
- 类型强转的计算量均视为 1
- >, >=, <, <=, !=, == 比较运算符的计算量为 0
- 赋值运算符的计算量视为 0
- 特殊赋值运算符(+=, -= 等)的计算量为 2
- 其他双元运算符(+, -, *, / ,&&, || 等)的计算量为 1
- 三元运算符(expr1 ? expr2 : expr3) 的计算量视为 expr1 + (expr2 + expr3) / 2
- if, switch, for, while 这些控制语句的计算量单独处理
- 普通语句的计算量逐条记录
- 块会统计块内普通语句的计算量之和,但作用域被打断时(例如:continue,break 或者子作用域),就立即统计之前累计的计算量。
-
有设想能够设置配置文件中的计算量,但有两个问题: (1)有些运算符的计算量是动态的,需要与语法配套使用,会更加复杂。 (2)用户自己设置的话,有更多的操作量。
-
有设想增加一个 op_config.yaml 配置文件,该文件中可以注册函数的计算量。
-
暂时无法识别类和结构体的声明
-
类型强转的写法只能是 '(' + type + ')' 的形式,不管实际有没有强转,计算量均视为 1。
-
变量声明初始化不支持(),{} 等写法
-
逻辑判断是希望不统计计算量,但 &&, || 的计算量视为 1
-
暂时无法识别模板和宏。<> 目前只能在函数调用中识别。
-
if 如果只跟单语句而没有带 {},暂时无法写到对应的作用域中
-
block 合并计算量写入在 "}" 上一行,如果之前语句有 continue, break, goto, return 等会使得统计量偏少。
-
期望寻址计算是不统计计算量,[] 之间是不考虑计算量,但是 * 解引用的寻址方法难以区分; 数组索引的计算最好不视作计算量,这也属于寻址计算,但是从语义角度难以区分,这部分计算量会统计起来。 TODO: 索引变量不统计计算量。
-
赋值语句的计算量均视为 0,是因为 cpu 写法中会用到大量的临时变量,这些都统计的话,会使得理论计算量较高 TODO:赋值运算如果是初始化运算量视为 0,非初始化运算量视为 1
-
只能识别命名空间函数,不支持命名空间其他的识别,不会统计这部分的计算量。 例如:无法识别 std::vector A
-
冗余写法的计算量也会统计,例如: 乘以 1, 除以 1,加减 0 仍然会统计计算量