修复开启边沿计数后拖动时间光标卡顿(增量统计)

症状:VERY_BIG_DATA.CSV 全选信号后拖动时间光标,未开启实验性功能时约 13ms/帧很顺畅,
一旦对某个网格开启「边沿计数」或「边沿计数(中心轴)」,拖动就变卡(实测约 24ms / 34ms 每帧)。

根因:两个边沿统计函数的缓存键里包含时间区间(rangeMin/rangeMax)。拖动时间光标时区间
每帧都在变,于是每帧全部缓存失效,对网格内每条可见信号重扫整段区间(亿级采样即数十毫秒);
同时缓存是无上限的 vector,每个新区间都会追加一条,拖一会儿就积累数百条,查找也退化为线性扫描。

修复:缓存键改为「数据分组 + 信号 + 判定基准(横向光标幅值 / 平滑半径)+ 是否限定区间」,
时间区间不再进键;缓存项改为记录「上一次统计的采样下标区间 [begin, end) 与其计数」:
- 区间完全一致 → 直接命中;
- 与上一帧共享一个端点(拖动光标时必然如此)→ 只扫描两端差集并增量加减;
- 两端都变 / 首次统计 / 时间轴非单调 → 整段重算。
另加缓存条目上限(48 条,超出淘汰最早一条),避免长时间拖动导致缓存无界增长。

新增的滑动区间基元 AccumulateEdgeCrossings(pairFirst, pairLast) 统计采样对 k ∈ [pairFirst,
pairLast](每对为 (k-1, k)),起点对的连续性由其前一个采样决定;这样把区间切成互不相交的
若干段分别累加(或从总量中减去)与一次性扫描整段完全等价,是增量加减的正确性依据。

验证(VERY_BIG_DATA、5 信号、双光标、网格 1 开启对应功能、反复拖动时间光标):
- 中心轴边沿计数 33.8ms → 13.3ms/帧;普通边沿计数 24.5ms → 11.3ms/帧;
- 用临时探针把增量结果与「整段重算」逐位比对(两个功能都覆盖),零 MISMATCH;
- 面板数值随拖动实时更新,单光标(无区间)走全信号统计并被缓存,横向光标与面板交互不变。
This commit is contained in:
DeepSeek V4.1 Flash committed 2026-10-10 09:32:59 +08:00
1 parent 350538c634
commit 79c84edea8
2 files changed
+297 -153

No files matched your search

+28 -18
View File
@@ -1235,17 +1235,26 @@ namespace jdi
std::vector<gd::RectF> m_cursorLabelRects; /**< 本次绘制已占用的取值标注矩形(用于避让重叠)。 */
std::vector<LaneVisual> m_lanes; /**< 当前网格绘制信息集合。 */
/** 边沿统计结果缓存项(按「文件 + 信号 + 横向光标幅值 + 时间区间」标识,任一变化即重算)。 */
/**
* @brief 边沿统计缓存项。
*
* 计数只由「采样下标区间 [begin, end) 与判定基准(横向光标幅值)」决定,
* 因此**时间区间不进缓存键**:拖动时间光标时区间每帧都在变,若按时间区间做键就会
* 每帧全量重扫整段区间(亿级采样下正是拖拽卡顿的主因,实测单帧数十毫秒)。
* 改为记住「上一帧统计过的下标区间」:新区间与旧区间共享一个端点时(拖动时必然如此)
* 只扫描两端差集并增量加减,单帧代价与光标位移成正比、与区间长度无关。
*/
struct EdgeCountEntry
{
const DataFile* file = nullptr;
const SignalData* signal = nullptr;
double edgeValue = 0.0;
bool hasRange = false;
double rangeMin = 0.0;
double rangeMax = 0.0;
std::size_t rising = 0;
std::size_t falling = 0;
const DataFile* file = nullptr; /**< 所属数据分组。 */
const SignalData* signal = nullptr; /**< 目标信号。 */
double edgeValue = 0.0; /**< 判定阈值(横向光标幅值)。 */
bool hasRange = false; /**< 是否限定在时间区间内统计。 */
bool reusable = false; /**< 计数是否完全由 [begin, end) 决定。 */
std::size_t begin = 0; /**< 已统计的采样区间起点(含)。 */
std::size_t end = 0; /**< 已统计的采样区间终点(不含)。 */
std::size_t rising = 0; /**< 该区间内的上升沿数量。 */
std::size_t falling = 0; /**< 该区间内的下降沿数量。 */
};
mutable std::vector<EdgeCountEntry> m_edgeCountCache; /**< 边沿统计缓存(纯缓存,逻辑上不影响对象状态)。 */
@@ -1259,17 +1268,18 @@ namespace jdi
/** 中心平均值曲线缓存:**deque** 保证 push_back 后已有元素引用仍然有效。 */
mutable std::deque<CenterCurveEntry> m_centerCurveCache;
/** 中心轴边沿统计缓存项。 */
/** 中心轴边沿统计缓存项(增量统计机制与 `EdgeCountEntry` 完全一致)。 */
struct EdgeCenterCountEntry
{
const DataFile* file = nullptr;
const SignalData* signal = nullptr;
int smooth = 0;
bool hasRange = false;
double rangeMin = 0.0;
double rangeMax = 0.0;
std::size_t rising = 0;
std::size_t falling = 0;
const DataFile* file = nullptr; /**< 所属数据分组。 */
const SignalData* signal = nullptr; /**< 目标信号。 */
int smooth = 0; /**< 中心平均值曲线的平滑半径 N。 */
bool hasRange = false; /**< 是否限定在时间区间内统计。 */
bool reusable = false; /**< 计数是否完全由 [begin, end) 决定。 */
std::size_t begin = 0; /**< 已统计的采样区间起点(含)。 */
std::size_t end = 0; /**< 已统计的采样区间终点(不含)。 */
std::size_t rising = 0; /**< 该区间内的上升沿数量。 */
std::size_t falling = 0; /**< 该区间内的下降沿数量。 */
};
mutable std::vector<EdgeCenterCountEntry> m_edgeCenterCountCache; /**< 中心轴边沿统计缓存。 */