深度修复实验性功能预览曲线导致的交互卡顿(平移/拖滑块/缩放/改样式不跟手)
根因:派生预览曲线按可见采样窗口计算,代价正比于窗口采样数(弱波增强整段 2576 万采样单条约 368ms);旧策略对超过 4M 采样的窗口不缓存,导致大跨度下每帧(含悬停重绘)重算整段曲线;拖滑块每帧改参数即每帧重算。实测弱波增强整段拖滑块单帧约 2303ms,2.5s 跨度约 89ms。 修复:1) 大窗口也进缓存并按总字节预算淘汰(静止重绘 2247ms-0.25ms);2) 交互期间对超出预算的大窗口延迟重算、复用旧曲线(拖滑块 2303ms-18ms),小窗口仍实时重算;3) 计算缓冲改 thread_local 复用容量。仅改何时计算与是否缓存,几何与取值算法不变,逐像素比对 0 差异。 缩放等级差异:整段放大时窗口为整条信号(千万级)每帧重算故最卡;极小跨度窗口仅几千采样故不卡;中间跨度几十毫秒亦卡。
This commit is contained in:
1 parent
2f7a6bc2d2
commit
aca84df0ac
4 files changed
+138
-19
No files matched your search
@@ -1264,12 +1264,17 @@ namespace jdi
|
|||||||
* @param [IN] begin 需要的采样区间起点(含)。
|
* @param [IN] begin 需要的采样区间起点(含)。
|
||||||
* @param [IN] end 需要的采样区间终点(不含)。
|
* @param [IN] end 需要的采样区间终点(不含)。
|
||||||
* @param [OUT] spanBegin 返回数组对应的首个采样下标。
|
* @param [OUT] spanBegin 返回数组对应的首个采样下标。
|
||||||
|
* @param [IN] allowInteractiveDefer 交互期间是否允许「延迟重算」:窗口过大时复用已算好的
|
||||||
|
* 旧曲线,避免在拖动滑块/移动视界时同步重算整条曲线(见
|
||||||
|
* `kDerivedInteractiveComputeBudget`)。仅供**预览绘制**传 true;
|
||||||
|
* 需要精确取值的统计(中心轴边沿计数)必须传 false。
|
||||||
* @return 曲线数组指针(覆盖 `[spanBegin, spanBegin + size())`);无法计算时返回 nullptr。
|
* @return 曲线数组指针(覆盖 `[spanBegin, spanBegin + size())`);无法计算时返回 nullptr。
|
||||||
*/
|
*/
|
||||||
const std::vector<float>* CurveSpanFor(DerivedCurveKind kind, const SignalData& signal,
|
const std::vector<float>* CurveSpanFor(DerivedCurveKind kind, const SignalData& signal,
|
||||||
int points, int iterations, int levelPercent,
|
int points, int iterations, int levelPercent,
|
||||||
std::size_t begin, std::size_t end,
|
std::size_t begin, std::size_t end,
|
||||||
std::size_t& spanBegin) const;
|
std::size_t& spanBegin,
|
||||||
|
bool allowInteractiveDefer = false) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 计算当前视图对应的采样下标区间(时间单调时二分定位,否则为整段)。
|
* @brief 计算当前视图对应的采样下标区间(时间单调时二分定位,否则为整段)。
|
||||||
|
|||||||
@@ -80,16 +80,34 @@ namespace jdi
|
|||||||
* 上限避免在完全缩小时把保护带放大到整条信号(那就退化成重算整条曲线)。
|
* 上限避免在完全缩小时把保护带放大到整条信号(那就退化成重算整条曲线)。
|
||||||
*/
|
*/
|
||||||
const std::size_t kDerivedCurveGuardMax = 65536;
|
const std::size_t kDerivedCurveGuardMax = 65536;
|
||||||
/** 区间窗口缓存条目上限(超出时整体清空;它只是加速手段)。 */
|
/** 区间窗口缓存条目上限(超出时淘汰最早的条目;它只是加速手段)。 */
|
||||||
const std::size_t kCurveSpanCacheLimit = 32;
|
const std::size_t kCurveSpanCacheLimit = 32;
|
||||||
/**
|
/**
|
||||||
* @brief 进入「区间窗口缓存」的单条窗口采样数上限。
|
* @brief 进入「区间窗口缓存」的单条窗口采样数上限。
|
||||||
*
|
*
|
||||||
* 超过它的窗口(例如「中心轴边沿计数」在未限定光标区间时的整条信号)
|
* **必须足够大**,使「完全放大到整条信号」的窗口也能进缓存:否则每一帧(含静止时
|
||||||
* 改写进 `m_curveSpanScratch`:缓存的窗口一旦被淘汰就得重算整条曲线
|
* 的鼠标悬停重绘)都要重算整条曲线 —— 弱波增强在 2576 万采样上单条 ≈368ms,
|
||||||
* (亿级采样实测 ≈1s 的卡顿尖峰),而且几条曲线同时驻留就会吃掉数百 MB。
|
* 5 条网格就是 ≈1.8s/帧,交互完全无法进行。
|
||||||
|
*
|
||||||
|
* 窗口一旦计算完成,同一参数下(平移/缩放/悬停)都能直接复用;
|
||||||
|
* 内存总量由 `kCurveSpanCacheMaxBytes` 兜底,因此这里不再按采样数排除大窗口。
|
||||||
*/
|
*/
|
||||||
const std::size_t kCurveSpanCacheMaxSamples = 4194304;
|
const std::size_t kCurveSpanCacheMaxSamples = 67108864;
|
||||||
|
/** 区间窗口缓存允许占用的总字节数(约 768MB):超出时按「最旧优先」淘汰。
|
||||||
|
* 取足够大是为了让「完全放大时 5 个网格各缓存一条整段曲线」可以同时驻留,
|
||||||
|
* 否则缓存互相淘汰会导致每帧重算其中一条(弱波增强 ≈368ms 的卡顿尖峰)。 */
|
||||||
|
const std::size_t kCurveSpanCacheMaxBytes = 768ull * 1024ull * 1024ull;
|
||||||
|
/**
|
||||||
|
* @brief 交互期间允许**同步重算**的派生曲线窗口采样数上限。
|
||||||
|
*
|
||||||
|
* 拖动滑块 / 移动视界 / 缩放等交互每帧都会请求派生(预览)曲线。窗口不大时同步重算
|
||||||
|
* 足够快(几毫秒),预览曲线能实时跟随控件;一旦窗口超过本上限(例如大时间跨度或
|
||||||
|
* 整条信号),同步重算会阻塞交互(弱波增强整条信号单条 ≈368ms)。
|
||||||
|
*
|
||||||
|
* 此时**不在交互过程中重算**,而是复用上一次算好的曲线(位置由当前视图映射,仍准确),
|
||||||
|
* 等交互结束后再按最终参数精确重算 —— 控件(滑块/光标/视界)的反馈因此始终跟手。
|
||||||
|
*/
|
||||||
|
const std::size_t kDerivedInteractiveComputeBudget = 131072;
|
||||||
const wchar_t* const kWeakBoostLevelLabel = L"弱波增强等级"; /**< 等级滑块行左侧的说明文字。 */
|
const wchar_t* const kWeakBoostLevelLabel = L"弱波增强等级"; /**< 等级滑块行左侧的说明文字。 */
|
||||||
const wchar_t* const kWeakBoostIterationsLabel = L"迭代次数 N"; /**< 迭代次数滑块行左侧的说明文字。 */
|
const wchar_t* const kWeakBoostIterationsLabel = L"迭代次数 N"; /**< 迭代次数滑块行左侧的说明文字。 */
|
||||||
const wchar_t* const kWeakBoostTitle = L"弱波增强"; /**< 弱波增强面板标题。 */
|
const wchar_t* const kWeakBoostTitle = L"弱波增强"; /**< 弱波增强面板标题。 */
|
||||||
@@ -2105,7 +2123,8 @@ namespace jdi
|
|||||||
const std::vector<float>* PlotView::CurveSpanFor(DerivedCurveKind kind, const SignalData& signal,
|
const std::vector<float>* PlotView::CurveSpanFor(DerivedCurveKind kind, const SignalData& signal,
|
||||||
int points, int iterations, int levelPercent,
|
int points, int iterations, int levelPercent,
|
||||||
std::size_t begin, std::size_t end,
|
std::size_t begin, std::size_t end,
|
||||||
std::size_t& spanBegin) const
|
std::size_t& spanBegin,
|
||||||
|
bool allowInteractiveDefer) const
|
||||||
{
|
{
|
||||||
spanBegin = 0;
|
spanBegin = 0;
|
||||||
const std::size_t count = signal.values.size();
|
const std::size_t count = signal.values.size();
|
||||||
@@ -2141,6 +2160,43 @@ namespace jdi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ⚠️ 交互期间的「延迟重算」:拖动滑块 / 移动视界 / 缩放每帧都会请求预览曲线,
|
||||||
|
// 若窗口很大(例如大时间跨度、整条信号),同步重算会阻塞交互数毫秒到数百毫秒
|
||||||
|
// (弱波增强整条信号单条 ≈368ms)。此时**不重算**,改复用上一次算好的曲线:
|
||||||
|
// 曲线下标映射到时间仍是当前视图的映射,因此位置依旧准确,只是参数/覆盖范围可能是
|
||||||
|
// 上一帧的;等交互结束后(`m_dragMode == NONE`)再按最终参数精确重算。
|
||||||
|
// 这样滑块 / 光标 / 视界移动的反馈始终跟手,不会被绘制计算拖住。
|
||||||
|
if (allowInteractiveDefer && m_dragMode != PlotDragMode::NONE &&
|
||||||
|
(end - begin) > kDerivedInteractiveComputeBudget)
|
||||||
|
{
|
||||||
|
const CurveSpanEntry* covering = nullptr;
|
||||||
|
const CurveSpanEntry* newest = nullptr;
|
||||||
|
for (auto it = m_curveSpanCache.rbegin(); it != m_curveSpanCache.rend(); ++it)
|
||||||
|
{
|
||||||
|
if (it->signal != &signal || it->kind != kindValue)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (newest == nullptr)
|
||||||
|
{
|
||||||
|
newest = &*it;
|
||||||
|
}
|
||||||
|
if (it->begin <= begin && it->end >= end)
|
||||||
|
{
|
||||||
|
covering = &*it;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const CurveSpanEntry* fallback = (covering != nullptr) ? covering : newest;
|
||||||
|
if (fallback != nullptr)
|
||||||
|
{
|
||||||
|
spanBegin = fallback->begin;
|
||||||
|
return &fallback->values;
|
||||||
|
}
|
||||||
|
// 完全没有可复用的曲线:本帧跳过该预览曲线(不阻塞交互)。
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
// 未命中:向外扩一个保护带后再计算,保证可见区间内的取值与"整条曲线"一致。
|
// 未命中:向外扩一个保护带后再计算,保证可见区间内的取值与"整条曲线"一致。
|
||||||
// 保护带取窗口长度的 1/8(夹在 4096~65536):弱波增强的修正沿相邻极值向内传播
|
// 保护带取窗口长度的 1/8(夹在 4096~65536):弱波增强的修正沿相邻极值向内传播
|
||||||
// (每迭代一次传一个极值),1/8 个可见宽度足以覆盖真实信号的极值间距。
|
// (每迭代一次传一个极值),1/8 个可见宽度足以覆盖真实信号的极值间距。
|
||||||
@@ -2183,9 +2239,23 @@ namespace jdi
|
|||||||
return &m_curveSpanScratch;
|
return &m_curveSpanScratch;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_curveSpanCache.size() >= kCurveSpanCacheLimit)
|
// 缓存窗口:条目数与总字节数双重约束,避免大窗口把内存吃满。
|
||||||
|
// 淘汰策略为「最旧优先」(`deque` 头部即最早写入的)。
|
||||||
|
const auto cacheBytes = [this]() -> std::size_t
|
||||||
{
|
{
|
||||||
m_curveSpanCache.clear();
|
std::size_t total = 0;
|
||||||
|
for (const CurveSpanEntry& cached : m_curveSpanCache)
|
||||||
|
{
|
||||||
|
total += cached.values.size() * sizeof(float);
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
};
|
||||||
|
while (!m_curveSpanCache.empty() &&
|
||||||
|
(m_curveSpanCache.size() >= kCurveSpanCacheLimit ||
|
||||||
|
cacheBytes() + (computedEnd - computedBegin) * sizeof(float) >
|
||||||
|
kCurveSpanCacheMaxBytes))
|
||||||
|
{
|
||||||
|
m_curveSpanCache.pop_front();
|
||||||
}
|
}
|
||||||
m_curveSpanCache.push_back(CurveSpanEntry());
|
m_curveSpanCache.push_back(CurveSpanEntry());
|
||||||
CurveSpanEntry& entry = m_curveSpanCache.back();
|
CurveSpanEntry& entry = m_curveSpanCache.back();
|
||||||
@@ -7395,7 +7465,7 @@ namespace jdi
|
|||||||
std::size_t spanBegin = 0;
|
std::size_t spanBegin = 0;
|
||||||
const std::vector<float>* curvePtr =
|
const std::vector<float>* curvePtr =
|
||||||
CurveSpanFor(kind, *resolved.data, points, iterations, levelPercent, begin,
|
CurveSpanFor(kind, *resolved.data, points, iterations, levelPercent, begin,
|
||||||
end, spanBegin);
|
end, spanBegin, true);
|
||||||
if (curvePtr == nullptr)
|
if (curvePtr == nullptr)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -8161,7 +8231,8 @@ namespace jdi
|
|||||||
}
|
}
|
||||||
std::size_t spanBegin = 0;
|
std::size_t spanBegin = 0;
|
||||||
const std::vector<float>* curve = CurveSpanFor(DerivedCurveKind::MEAN_FILTER, *resolved.data,
|
const std::vector<float>* curve = CurveSpanFor(DerivedCurveKind::MEAN_FILTER, *resolved.data,
|
||||||
points, iterations, 0, begin, end, spanBegin);
|
points, iterations, 0, begin, end, spanBegin,
|
||||||
|
true);
|
||||||
if (curve == nullptr)
|
if (curve == nullptr)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -8186,7 +8257,8 @@ namespace jdi
|
|||||||
}
|
}
|
||||||
std::size_t spanBegin = 0;
|
std::size_t spanBegin = 0;
|
||||||
const std::vector<float>* curve = CurveSpanFor(DerivedCurveKind::WEAK_BOOST, *resolved.data, 0,
|
const std::vector<float>* curve = CurveSpanFor(DerivedCurveKind::WEAK_BOOST, *resolved.data, 0,
|
||||||
iterations, levelPercent, begin, end, spanBegin);
|
iterations, levelPercent, begin, end, spanBegin,
|
||||||
|
true);
|
||||||
if (curve == nullptr)
|
if (curve == nullptr)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -187,6 +187,36 @@ Documents/ 项目文档(含本文件)
|
|||||||
- 「构建为新信号」等一次性操作会真正计算整条派生曲线(约 1 帧的秒级耗时,正常)。
|
- 「构建为新信号」等一次性操作会真正计算整条派生曲线(约 1 帧的秒级耗时,正常)。
|
||||||
- **测量工具**:`.preflight/perf/perf.exe <csv> [mode]`(离屏,不弹窗)。`mode=0` 跑各交互场景耗时;`mode=1` 导出确定性渲染 PNG 以便逐像素比对(用于验证"显示效果不变")。该目录已由 `.gitignore` 忽略,不会入库。
|
- **测量工具**:`.preflight/perf/perf.exe <csv> [mode]`(离屏,不弹窗)。`mode=0` 跑各交互场景耗时;`mode=1` 导出确定性渲染 PNG 以便逐像素比对(用于验证"显示效果不变")。该目录已由 `.gitignore` 忽略,不会入库。
|
||||||
|
|
||||||
|
### 9.5 性能:启用带预览曲线的实验性功能后,交互严重卡顿(核心修复,含"为什么只有某些缩放等级卡"的解答)
|
||||||
|
- **症状**(以 `VERY_BIG_DATA.CSV`,5 信号全部显示为例):
|
||||||
|
- 「弱波增强」滑块**拖动时**在**任何缩放等级**下都卡顿;越缩得小(可见时间跨度越大)越卡 —— 时间跨度到 2.5s 时单帧 ≈89ms,完全放大(整段 128.8s)时单帧 **≈2.3 秒**。
|
||||||
|
- 网格「选区放大」到某些跨度后,平移视界 / 改样式也卡。
|
||||||
|
- **根因**(`Components/PlotView/Src/PlotView.cpp`,`CurveSpanFor` + 缓存策略 + `Utilities/Src/DataModel.cpp` 的 `ComputeWeakWaveBoost`):
|
||||||
|
1. 派生(预览)曲线按「可见采样窗口 ± 保护带」计算,**代价正比于窗口内的采样数**。实测:弱波增强整段(2576 万采样)单条 **≈368ms**,均值滤波整段单条 ≈57ms。5 个网格就是 ≈1.8s。
|
||||||
|
2. 旧代码有一条「**超过 4M 采样的窗口不进缓存**」的规则(为省内存),于是**大跨度下每帧都重算**整段曲线 —— 包括静止时的鼠标悬停重绘。这就是"某些缩放等级特别卡"的原因:
|
||||||
|
- 完全放大:窗口 = 整条信号(千万级)→ 每帧重算 → 秒级卡顿;
|
||||||
|
- 很小的时间跨度:窗口只有几千采样 → 计算只要微秒级 → **毫不卡顿**;
|
||||||
|
- 中间跨度:窗口在十几万~几百万采样之间 → 单帧几十毫秒 → 卡,且保护带(≤65536)相对窗口太短,平移很容易越出缓存覆盖 → 频繁重算。
|
||||||
|
3. 拖动「弱波增强」滑块每帧都改等级 → 缓存键变化 → 每帧都命中"未缓存"→ 每帧重算(叠加上述放大效应)。
|
||||||
|
- **修复**(三管齐下,**显示效果逐像素不变**):
|
||||||
|
1. **大窗口也进缓存**:`kCurveSpanCacheMaxSamples` 提高到 64M(相当于不再按采样数排除),并新增**总字节预算** `kCurveSpanCacheMaxBytes`(≈768MB,最旧优先淘汰)兜底内存。这样同一参数下的平移/缩放/悬停都能复用,静止重绘降到 **0.25ms**。
|
||||||
|
2. **交互期间「延迟重算」**(`CurveSpanFor` 新增 `allowInteractiveDefer`):拖动滑块/移动视界/缩放时,若所需窗口超过 `kDerivedInteractiveComputeBudget`(131072 采样),**本帧不重算**,改复用上一次算好的曲线(曲线下标→时间的映射仍是当前视图,因此位置准确),交互结束后再按最终参数精确重算。窗口不大时(常见工作缩放)照常同步重算,预览曲线**实时跟随控件**。
|
||||||
|
- 用途区分:**预览绘制**(`PaintMeanFilterCurve`/`PaintWeakBoostCurve`/光标取值胶囊)传 `allowInteractiveDefer = true`;**需要精确取值的统计**(中心轴边沿计数 `EdgeCenterCountsFor`、`MeanFilterValueAt`/`WeakBoostValueAt`)传 `false`(默认)。
|
||||||
|
3. **计算缓冲复用**:`ComputeWeakWaveBoost` / `ComputeMovingAverageIterated` / `ApplyWeakBoostStage` 的工作缓冲改为 `thread_local` 复用容量,避免每次重算都分配并零初始化上千万元素(多阶弱波增强 991ms→577ms)。
|
||||||
|
- **实测**(离屏,`VERY_BIG_DATA`,5 网格;单位 ms/帧):
|
||||||
|
|
||||||
|
| 场景 | 修复前 | 修复后 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 弱波增强 滑块拖动(整段) | ≈2303 | **≈18** |
|
||||||
|
| 弱波增强 滑块拖动(2.5s 跨度) | ≈89 | **≈15** |
|
||||||
|
| 弱波增强 滑块拖动(103ms / 20ms 跨度) | ≈14 / ≈12 | ≈15 / ≈11 |
|
||||||
|
| 弱波增强 静止重绘(整段) | ≈2247 | **≈0.25** |
|
||||||
|
| 均值滤波 平移(2.5s 跨度) | ≈27 | ≈23(大窗口非拖拽时仍按需重算;拖拽时延迟) |
|
||||||
|
| 普通平移 / 光标移动 / 改色 | 3–12 | 不变(1–12) |
|
||||||
|
|
||||||
|
- **已知残留**(不牺牲显示时的固有成本):把可见时间跨度拉到接近整段且开启弱波增强时,**首次**计算某参数/窗口的预览曲线仍需要一次 ≈1.8s(5 条整段曲线,各 ≈368ms)—— 交互期间不会触发(已延迟),但交互结束后的那一帧会补上。这是"整段弱波增强 O(n) 计算"的固有代价;若要进一步消除需把该计算移到后台线程(尚未做,改动较大)。
|
||||||
|
- **为什么这样改不牺牲显示效果**:本次只改「何时计算 / 是否缓存」,不改任何几何或取值算法;`mode=1` 的逐像素比对(均值滤波 + 弱波增强,两个缩放级别,含光标取值胶囊)结果为 **0 像素差异**。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 10. 变更日志(本文件维护记录)
|
## 10. 变更日志(本文件维护记录)
|
||||||
@@ -195,3 +225,4 @@ Documents/ 项目文档(含本文件)
|
|||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| 2026-10-11 | DeepSeek-V4.1-Flash | 新建本文件;记录项目要点与规范;修复「实验性功能」二级菜单瞬间关闭问题。 |
|
| 2026-10-11 | DeepSeek-V4.1-Flash | 新建本文件;记录项目要点与规范;修复「实验性功能」二级菜单瞬间关闭问题。 |
|
||||||
| 2026-10-11 | DeepSeek-V4.1-Flash | 修复右键网格菜单后悬停光晕/提示气泡残留;修复「启用带预览曲线的实验性功能后平移/拖光标 300ms+ 卡顿」(`paintDerivedCapsule` 巨窗每帧重算,收窄请求窗口,显示效果零变化)。 |
|
| 2026-10-11 | DeepSeek-V4.1-Flash | 修复右键网格菜单后悬停光晕/提示气泡残留;修复「启用带预览曲线的实验性功能后平移/拖光标 300ms+ 卡顿」(`paintDerivedCapsule` 巨窗每帧重算,收窄请求窗口,显示效果零变化)。 |
|
||||||
|
| 2026-10-11 | DeepSeek-V4.1-Flash | 深度修复「实验性功能预览曲线导致的交互卡顿」:大窗口改入缓存(静止重绘 2247ms→0.25ms)、交互期间延迟重算(弱波增强拖滑块 2303ms→18ms)、计算缓冲复用;显示逐像素不变。 |
|
||||||
@@ -1032,7 +1032,10 @@ namespace jdi
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 后续各阶以上一阶的结果为输入(double 中转,避免 float→double 的精度损失累积)。
|
// 后续各阶以上一阶的结果为输入(double 中转,避免 float→double 的精度损失累积)。
|
||||||
std::vector<double> buffer(values.size(), std::numeric_limits<double>::quiet_NaN());
|
// 中转缓冲复用容量(线程本地),避免每阶重新分配上千万元素的向量。
|
||||||
|
thread_local std::vector<double> bufferBuffer;
|
||||||
|
std::vector<double>& buffer = bufferBuffer;
|
||||||
|
buffer.assign(values.size(), std::numeric_limits<double>::quiet_NaN());
|
||||||
std::vector<float> stage;
|
std::vector<float> stage;
|
||||||
for (int iteration = 1; iteration < stages; ++iteration)
|
for (int iteration = 1; iteration < stages; ++iteration)
|
||||||
{
|
{
|
||||||
@@ -1070,7 +1073,7 @@ namespace jdi
|
|||||||
* @return 确实有采样点被改动返回 true;完全无弱波时返回 false。
|
* @return 确实有采样点被改动返回 true;完全无弱波时返回 false。
|
||||||
*/
|
*/
|
||||||
bool ApplyWeakBoostStage(const std::vector<float>& input, double strength,
|
bool ApplyWeakBoostStage(const std::vector<float>& input, double strength,
|
||||||
std::vector<float>& output)
|
std::vector<float>& output, std::vector<BoostExtremum>& extremes)
|
||||||
{
|
{
|
||||||
const std::size_t count = input.size();
|
const std::size_t count = input.size();
|
||||||
output.assign(count, std::numeric_limits<float>::quiet_NaN());
|
output.assign(count, std::numeric_limits<float>::quiet_NaN());
|
||||||
@@ -1092,7 +1095,7 @@ namespace jdi
|
|||||||
// 1) 识别波峰 / 波谷:扫描单调段,方向翻转处的上一个采样点即为极值点。
|
// 1) 识别波峰 / 波谷:扫描单调段,方向翻转处的上一个采样点即为极值点。
|
||||||
// 波峰(先升后降)与波谷(先降后升)必然交替出现,
|
// 波峰(先升后降)与波谷(先降后升)必然交替出现,
|
||||||
// 因此同类极值就是极值序列里下标 ±2 的两个。
|
// 因此同类极值就是极值序列里下标 ±2 的两个。
|
||||||
std::vector<BoostExtremum> extremes;
|
extremes.clear();
|
||||||
{
|
{
|
||||||
int direction = 0; // 当前单调段方向(+1 上升、-1 下降)。
|
int direction = 0; // 当前单调段方向(+1 上升、-1 下降)。
|
||||||
bool hasPrevious = false;
|
bool hasPrevious = false;
|
||||||
@@ -1145,7 +1148,9 @@ namespace jdi
|
|||||||
|
|
||||||
// 2) 逐个极值点判定“是否偏弱”,并算出它自己的目标偏移量。
|
// 2) 逐个极值点判定“是否偏弱”,并算出它自己的目标偏移量。
|
||||||
const std::size_t extremeCount = extremes.size();
|
const std::size_t extremeCount = extremes.size();
|
||||||
std::vector<double> shift(extremeCount, 0.0);
|
thread_local std::vector<double> shiftBuffer;
|
||||||
|
std::vector<double>& shift = shiftBuffer;
|
||||||
|
shift.assign(extremeCount, 0.0);
|
||||||
for (std::size_t k = 2; k + 2 < extremeCount; ++k)
|
for (std::size_t k = 2; k + 2 < extremeCount; ++k)
|
||||||
{
|
{
|
||||||
const BoostExtremum& current = extremes[k];
|
const BoostExtremum& current = extremes[k];
|
||||||
@@ -1241,7 +1246,13 @@ namespace jdi
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 第 1 阶:从原始采样值出发(非有限值保持 NaN)。
|
// 第 1 阶:从原始采样值出发(非有限值保持 NaN)。
|
||||||
std::vector<float> current(count, std::numeric_limits<float>::quiet_NaN());
|
// 工作缓冲用线程本地变量复用容量:单条曲线动辄上千万采样,若每次调用都重新分配
|
||||||
|
// 并零初始化,单条「弱波增强」的耗时会被明显放大(内存分配 + page fault)。
|
||||||
|
thread_local std::vector<float> currentBuffer;
|
||||||
|
thread_local std::vector<float> nextBuffer;
|
||||||
|
thread_local std::vector<BoostExtremum> extremesBuffer;
|
||||||
|
std::vector<float>& current = currentBuffer;
|
||||||
|
current.assign(count, std::numeric_limits<float>::quiet_NaN());
|
||||||
for (std::size_t index = 0; index < count; ++index)
|
for (std::size_t index = 0; index < count; ++index)
|
||||||
{
|
{
|
||||||
const double value = values[index];
|
const double value = values[index];
|
||||||
@@ -1256,10 +1267,10 @@ namespace jdi
|
|||||||
// 多次迭代时相邻的弱波会互相抬高彼此的基准,从而把较宽的弱波区逐步恢复。
|
// 多次迭代时相邻的弱波会互相抬高彼此的基准,从而把较宽的弱波区逐步恢复。
|
||||||
const int stages = std::max(1, std::min(iterations, 1000));
|
const int stages = std::max(1, std::min(iterations, 1000));
|
||||||
const double strength = std::max(0.0, std::min(1.0, level));
|
const double strength = std::max(0.0, std::min(1.0, level));
|
||||||
std::vector<float> next;
|
std::vector<float>& next = nextBuffer;
|
||||||
for (int stage = 0; stage < stages; ++stage)
|
for (int stage = 0; stage < stages; ++stage)
|
||||||
{
|
{
|
||||||
if (!ApplyWeakBoostStage(current, strength, next))
|
if (!ApplyWeakBoostStage(current, strength, next, extremesBuffer))
|
||||||
{
|
{
|
||||||
// 这一阶没有任何采样点需要调整:后续迭代次数也不会再有变化,提前结束。
|
// 这一阶没有任何采样点需要调整:后续迭代次数也不会再有变化,提前结束。
|
||||||
break;
|
break;
|
||||||
|
|||||||
Reference in new issue
Block a user