15 KiB
15 KiB
统一表达式引擎设计:消除双 FunVars 和 is_exp_alg_ 补丁
针对 ExpBase::fun_vars_ 与 ExpModule::fun_vars_ 双重存在、is_exp_alg_ 补丁标志的历史遗留问题的根治方案
一、现状问题精准画像
1.1 重复代码对照
ExpBase::refresh_exp_vars_mem() ExpModule::refresh_exp_vars_mem()
───────────────────────────────── ─────────────────────────────────
更新 tagN ← 共享内存 更新 tagN ← 共享内存
更新 pN ← 旧 tagN 更新 pN ← 旧 tagN
更新 pvN_0..pvN_5 历史移位 更新 pvN_0..pvN_5 历史移位
更新 "now" ← epoch ms 更新 "now" ← epoch ms
调用 fun_vars_.refresh_fun_vars(false) (在 update() 中调用 fun_vars_.refresh_fun_vars(false))
调用 refresh_hold_var() (无 hold_var 处理)
设置 query_time_range_ (无)
两段代码做几乎相同的事,操作同一个 mm_vars,属于典型的 Cargo Cult 编程。
ExpBase::first_fill_mm_vars() ExpModule::init()
───────────────────────────────── ──────────────────
初始化所有 tagN, pN, pvN_0..pvN_5 初始化所有 tagN, pN, pvN_0..pvN_5
初始化 stime, now, etime, time 初始化 "now"
初始化 sN, mx_tagN, mi_tagN (无)
初始化 mv2_tagN, up_tagN, dw_tagN (无)
也是重复。
1.2 双 FunVars 的冲突风险
ExpBase::fun_vars_ ExpModule::fun_vars_
───────────────────── ─────────────────────
管理表达式: 管理表达式:
exp_act_ (带状态函数替换) pre_result (带状态函数替换)
exp_feedback_ (带状态函数替换)
exp_result_ (带状态函数替换)
pre_result (通过 fun_vars_init 共享)
内部index: 独立计数 内部index: 独立计数
创建的 mm_vars 变量: fun0,fun1,... 创建的 mm_vars 变量: fun0,fun1,...
冲突场景:
- ExpModule::add_exp("pre_result", "KeepC(tag1,1)") → 创建 mm_vars["fun0"]
- ExpBase::fun_vars_init() → fun_vars_.add_exp_str("KeepC(tag1,1)") → 也创建 mm_vars["fun0"]!
两个 FunVars 实例的 index 都从 0 开始,会向 mm_vars 写入同名的 fun0 变量。当 is_exp_alg_=true 时 ExpModule 跳过刷新,所以两个 fun0 不会在同一周期冲突。但如果某天代码修改导致两个 FunVars 都活跃,就会产生难以排查的 bug。
1.3 is_exp_alg_ 传递链的脆弱性
AlgBase::is_exp_alg_ = false (默认)
│
│ 引用传递
▼
ExpModule::is_exp_alg_& ← 持有 AlgBase 成员的引用
│
│ ExpBase 构造时
▼
AlgBase::is_exp_alg_ = true
│
│ ExpModule 通过引用看到变化
▼
ExpModule::is_exp_alg_ == true → 跳过变量刷新
这条引用链的问题:
- ExpModule 的生命周期依赖 AlgBase(引用不能悬挂)
is_exp_alg_被当作"表达式算法"的标记,实际上它是"是否需要 ExpModule 管理变量刷新"的补丁- 新算法开发者需要理解这个隐式约定才能正确设置
二、统一方案:ExpressionEngine
2.1 核心思路
让 ExpModule 吃掉 ExpBase 的表达式管理职责,成为唯一的表达式引擎。
BEFORE (现状): AFTER (统一后):
AlgBase AlgBase
├── exp_mpdule_ptr_ (ExpModule) └── expr_engine_ (ExpressionEngine)
│ ├── PRR 表达式 ├── 所有命名表达式
│ ├── 自己的 FunVars │ ├── "pre_result" (PRR)
│ └── 变量刷新 (当 is_exp_alg_=false) │ ├── "act" (前提表达式)
│ │ ├── "feedback" (反馈表达式)
ExpBase (is_exp_alg_=true) │ ├── "result" (结果表达式)
├── exp_act_ (独立的 Expression) │ ├── "sample_X", "sample_Y"
├── exp_feedback_ (独立的 Expression) │ ├── "dataX"
├── exp_result_ (独立的 Expression) │ └── "X1".."X9" (Roller2)
├── 自己的 FunVars ├── 唯一的 FunVars
├── 变量刷新 ├── 唯一的变量刷新
└── 自己的表达式配置加载 └── 统一的配置加载
2.2 ExpressionEngine 完整设计
// expression_engine.h
// 统一表达式引擎 —— 替代 ExpModule + ExpBase 的表达式管理
class ExpressionEngine {
public:
// ========== 构造 ==========
ExpressionEngine(std::map<std::string, double>& mm_vars,
std::vector<std::string>& m_tags);
// ========== 表达式注册 ==========
// 注册一个命名表达式。返回 0 成功,-1 失败。
// exp_name: "act", "feedback", "result", "sample_X", "sample_Y",
// "dataX", "X1"..."X9", "pre_result" 等
// raw_exp_str: 原始表达式字符串(含 KeepC(tag1,1) 等)
// use_fun_vars: 是否启用状态函数替换(默认 true)
int registerExpression(const std::string& exp_name,
const std::string& raw_exp_str,
bool use_fun_vars = true);
// ========== 每周期数据刷新 ==========
// 从共享内存刷新变量 + fun_vars
void refreshFromMemory();
// 从 IHDB 行数据刷新变量 + fun_vars
void refreshFromIhdRow(int row, const Eigen::MatrixXd& data,
const std::vector<TimePoint>& times);
// 首次填充(冷启动)
void firstFill(int data_source);
// ========== 表达式求值 ==========
double evaluate(const std::string& exp_name);
bool evaluateBool(const std::string& exp_name);
// ========== FunVars 控制 ==========
// 每周期调用一次(通常在 mon_proc 开头)
void autoResetFunVars();
// 标记下周期需要重置
void markFunVarsNeedReset();
// 立即强制重置(报警后调用)
void forceResetFunVars();
// ========== hold(n,T) 变量 ==========
void refreshHoldVars();
// ========== 查询 ==========
bool hasExpression(const std::string& exp_name) const;
std::string getExpressionStr(const std::string& exp_name) const;
std::map<std::string, double>& vars() { return mm_vars_; }
private:
std::map<std::string, double>& mm_vars_;
std::vector<std::string>& m_tags_;
// 唯一的 FunVars 实例
StatExp::FunVars fun_vars_;
bool fun_vars_need_reset_ = false;
// 表达式注册表
std::map<std::string, std::string> exp_raw_strs_; // 原始表达式字符串
std::map<std::string, std::string> exp_processed_; // FunVars 处理后的字符串
std::map<std::string, std::unique_ptr<MExp>> exp_ptrs_; // 编译后的表达式
// hold 变量管理
std::map<std::string, std::unique_ptr<HoldTime>> hold_times_;
// 变量缓存(pv 键名管理)
VarsCache var_cache_;
static constexpr size_t PV_NUM = 6;
// 内部方法
void doRefreshVarsMemory();
void doRefreshVarsIhd(int row, const Eigen::MatrixXd& data,
const std::vector<TimePoint>& times);
int initHoldExpStr(const std::string& exp_str);
};
2.3 实现要点
int ExpressionEngine::registerExpression(const std::string& exp_name,
const std::string& raw_exp_str,
bool use_fun_vars) {
if (exp_ptrs_.count(exp_name)) return 0; // 已注册
exp_raw_strs_[exp_name] = raw_exp_str;
std::string processed = raw_exp_str;
if (use_fun_vars) {
auto [ok, result] = fun_vars_.add_exp_str(raw_exp_str, &mm_vars_);
if (!ok) return -1;
processed = result;
}
exp_processed_[exp_name] = processed;
exp_ptrs_[exp_name] = std::make_unique<MExp>(processed, &mm_vars_);
return 0;
}
void ExpressionEngine::refreshFromMemory() {
// 1. 刷新 tag/pv 变量(统一的,只做一次)
for (size_t i = 0; i < m_tags_.size(); i++) {
double current = SingletonTemplate<GlobaltemSharedMemory>::GetInstance()[m_tags_[i]];
auto idx = std::to_string(i + 1);
mm_vars_[var_cache_.p_keys[i]] = mm_vars_[var_cache_.tag_keys[i]];
mm_vars_[var_cache_.tag_keys[i]] = current;
for (size_t j = PV_NUM - 1; j > 0; j--) {
mm_vars_[var_cache_.pv_keys[i][j]] = mm_vars_[var_cache_.pv_keys[i][j - 1]];
}
mm_vars_[var_cache_.pv_keys[i][0]] = current;
}
auto now = system_clock::now();
mm_vars_["now"] = duration_cast<milliseconds>(now.time_since_epoch()).count();
// 2. 刷新 fun_vars(只做一次,处理所有表达式的状态函数)
fun_vars_.refresh_fun_vars(false, &mm_vars_);
// 3. 刷新 hold 变量
refreshHoldVars();
}
void ExpressionEngine::autoResetFunVars() {
if (fun_vars_need_reset_) {
fun_vars_.refresh_fun_vars(true, &mm_vars_);
fun_vars_need_reset_ = false;
}
}
2.4 各算法使用 ExpressionEngine 的方式
// ========== AlgBase ==========
class AlgBase {
protected:
std::unique_ptr<ExpressionEngine> expr_engine_; // 替代 exp_mpdule_ptr_
// 删除: is_exp_alg_
};
// AlgBase::init() 中:
expr_engine_ = std::make_unique<ExpressionEngine>(mm_vars, m_tags);
if (prr_ == 1) {
expr_engine_->registerExpression("pre_result", pre_exp_str);
}
// AlgBase::exec_mon_call() 中:
if (alarm.alarmed) {
expr_engine_->forceResetFunVars(); // 替代 exp_mpdule_ptr_->fun_reset()
}
// AlgBase::get_prr() 中:
expr_engine_->refreshFromMemory(); // 统一刷新(不再有 is_exp_alg_ 判断)
bool prr_result = expr_engine_->evaluateBool("pre_result");
// ========== ExpBase (expression algorithms) ==========
// init() 中:
expr_engine_->registerExpression("act", exp_str);
if (feedback_mode_) {
expr_engine_->registerExpression("feedback", fb_exp_str);
}
expr_engine_->registerExpression("result", result_exp_str);
expr_engine_->firstFill(data_source_);
// exec_mon() 中:
expr_engine_->refreshFromMemory(); // 统一刷新
// ... 然后 mon_proc() 使用 expr_engine_->evaluate("act") 等
// ========== TrendSlope2 (non-expression algorithm) ==========
// 只需要 PRR:
// 在 AlgBase::get_prr() 中 expr_engine_->evaluateBool("pre_result")
// 自身的数据获取不受影响
// ========== Roller2 (multi-expression) ==========
// init() 中:
expr_engine_->registerExpression("pre_exp", pre_str);
expr_engine_->registerExpression("X1", x1_str);
expr_engine_->registerExpression("X2", x2_str);
// ...
三、消除项清单
统一后,以下内容可以删除:
从 ExpBase 删除
| 删除项 | 行数 | 说明 |
|---|---|---|
ExpBase::fun_vars_ |
1 行声明 | 合并到 ExpressionEngine |
ExpBase::refresh_exp_vars_mem() |
~25 行 | 合并到 ExpressionEngine::refreshFromMemory() |
ExpBase::refresh_exp_vars_ihd() |
~45 行 | 合并到 ExpressionEngine::refreshFromIhdRow() |
ExpBase::first_fill_mm_vars() |
~110 行 | 合并到 ExpressionEngine::firstFill() |
ExpBase::auto_fun_vars_reset() |
~15 行 | 合并到 ExpressionEngine::autoResetFunVars() |
ExpBase::fun_vars_init() |
~6 行 | 不再需要(注册时自动处理) |
ExpBase::refresh_hold_var() |
~10 行 | 合并到 ExpressionEngine |
ExpBase::init_hold_exp_str() |
~30 行 | 合并到 ExpressionEngine |
ExpBase::exp_messy_code_check() |
~8 行 | 合并到 ExpressionEngine |
exp_act_, exp_feedback_, exp_result_ |
3 个成员 | 改为通过 expr_engine_->evaluate("act") 等 |
从 ExpModule 删除
| 删除项 | 说明 |
|---|---|
| 整个 ExpModule 类 | 被 ExpressionEngine 完全替代 |
从 AlgBase 删除
| 删除项 | 说明 |
|---|---|
is_exp_alg_ |
补丁标志不再需要 |
exp_mpdule_ptr_ |
替换为 expr_engine_ |
总计消除
- 重复的变量刷新代码:~180 行(ExpBase+ExpModule 各一套)
- 重复的 FunVars 实例:1 个
- 补丁标志
is_exp_alg_:1 个布尔 + 引用传递链 - ExpModule 整个类:~140 行(被 ExpressionEngine 取代)
- ExpBase 可精简:~260 行
四、实施路径
阶段一:创建 ExpressionEngine(1 天)
- 新建
eqpalg/utility/expression_engine.h/.cpp - 将 ExpBase 的变量刷新逻辑(
refresh_exp_vars_mem/ihd、first_fill_mm_vars)迁入 - 将 ExpBase 的 FunVars 管理(
auto_fun_vars_reset、refresh_hold_var、init_hold_exp_str)迁入 - 将 ExpModule 的表达式注册机制(
add_exp、update)迁入并重命名为registerExpression/evaluate - 此阶段不修改 AlgBase/ExpBase,仅创建新类并编写独立单元测试
阶段二:替换 ExpModule(1 天)
- 将 AlgBase 中的
exp_mpdule_ptr_替换为expr_engine_ - 适配
AlgBase::init()中的 PRR 注册 - 适配
AlgBase::get_prr()和AlgBase::exec_mon_call()中的 fun_reset 调用 - 适配所有非 ExpBase 算法(TrendSlope2/3、Roller、FaultCode、GlitchDetection)
- 删除 ExpModule 类
- 删除
is_exp_alg_标志 - 运行非 ExpBase 算法的回归测试
阶段三:替换 ExpBase 的表达式管理(1-2 天)
- 将 ExpBase 中的
exp_act_/exp_feedback_/exp_result_改为通过expr_engine_->evaluate()访问 - 删除 ExpBase 的
fun_vars_、变量刷新方法、auto_fun_vars_reset等 - 适配 ExpBase 的所有子类(ExpBound、ExpTimes、ExpSample2D、Roller2、Roller3)
- 运行全量回归测试
阶段四:清理(0.5 天)
- 删除 ExpBase 中不再需要的成员和方法
- 删除
exp_base.cpp中 ~260 行已迁移代码 - 更新 build_algorithm.cpp 如需要
五、收益评估
| 指标 | 优化前 | 优化后 |
|---|---|---|
| FunVars 实例数 | 2 | 1 |
| 变量刷新代码份数 | 2(ExpBase + ExpModule) | 1(ExpressionEngine) |
is_exp_alg_ 补丁 |
存在 | 消除 |
| 表达式管理入口 | 3 处(ExpBase独立3成员 + ExpModule map) | 1 处(ExpressionEngine 统一 map) |
| 新算法接入方式 | 需了解 is_exp_alg_ 约定 | 直接 registerExpression |
| 状态函数冲突风险 | 存在(同名 funN) | 消除 |
| ExpModule 类 | 存在 | 删除 |