迭代 121 - 性能优化分析报告(第二轮)¶
背景¶
基于迭代 120 的优化后,重新对比 master 分支与 development 分支的性能差异,识别剩余热点并给出进一步优化建议。
对比基准(最新 2026-01-17)¶
master:
logs/performance_profile_master_20260117_093200.log(563.25s)development:
logs/performance_profile_development_20260117_093551.log(554.07s)🎉 当前状态: development 比 master 快 1.6%(-9.18s)
重大进展: 经过前期优化,development 分支性能已超越 master 分支!
测试状态: ✅ 所有 478 个测试通过,
scripts/optimize_code.sh验证通过
历史对比(旧数据参考)¶
master (旧):
logs/performance_profile_master_20260116_214606.log(511.51s)development (旧):
logs/performance_profile_development_20260117_080256.log(658.96s)性能差距 (旧): +147.45s (+28.8%)
关键发现¶
1. 内置函数调用量剧增¶
| 函数 | master 调用次数 | development 调用次数 | 增幅 |
|——|—————–|———————|——|
| builtins.len | 34.84M | 74.46M | +114% |
| builtins.isinstance | 45.38M | 104.29M | +130% |
| builtins.hasattr | N/A | 38.48M | 新增热点 |
| builtins.any | 8.76M | 9.32M | +6% |
| builtins.getattr | 40.21M | 61.92M | +54% |
分析*: development 分支的代码重构引入了大量额外的类型检查和属性探测调用,这是性能下降的主要原因之一。
2. LineSeries/LineBuffer forward 调用开销¶
| 函数 | master cumtime | development cumtime | 增幅 |
|——|—————-|———————|——|
| lineseries.py:forward | 35.77s (4.97M calls) | 33.52s + 32.36s (2.1M calls × 2) | 结构变化 |
| linebuffer.py:forward | 29.06s (17.27M calls) | 28.95s (10.73M calls) | 类似 |
| lineseries.py:__setattr__ | N/A | 16.46s (21.27M calls) | 新增热点 |
| lineseries.py:__len__ | 22.08s (16.02M calls) | 7.61s (7.37M calls) | -65% ✓ |
分析*:
LineSeries.__setattr__是新热点,说明属性设置逻辑引入了额外开销。
3. 指标计算效率¶
| 函数 | master | development | 说明 |
|——|——–|————-|——|
| bollinger.py:once | N/A | 22.07s (12 calls) | O(N*period) 嵌套循环 |
| sma.py:once | N/A | 18.60s (210 calls) | 每次调用 genexpr |
| sma.py:<genexpr> | N/A | 14.00s (27.33M calls) | SMA 内循环 |
分析*: 指标的
once()方法仍使用低效实现,特别是 Bollinger Bands 和 SMA。
4. 参数系统开销¶
| 函数 | 调用次数 | tottime | 说明 |
|——|———-|———|——|
| parameters.py:get_param | 7.41M | 3.81s | 仍有大量调用 |
| parameters.py:283(get) | 9.65M | 2.27s | ParameterManager.get |
| metabase.py:1581(p) | 16.25M | 4.05s | 属性代理 |
分析*: 迭代 120 的本地缓存有效,但其他热路径仍频繁调用参数系统。
优化建议¶
建议 A(高优先级):优化 LineSeries.setattr¶
热点*:
lineseries.py:1344(__setattr__)- 21.27M 次调用,16.46s 累计时间问题*: 当前实现对每次属性设置都执行多重类型检查和
hasattr/isinstance调用。落地方向*:
# 当前实现(伪代码):
def __setattr__(self, name, value):
if name and name[0] == "_": # OK - fast path
object.__setattr__(self, name, value)
return
# ... 多重 isinstance/hasattr 检查 ...
# 优化建议:
# 1. 扩大快速路径覆盖范围
# 2. 使用 type(value) 替代 isinstance() 进行简单类型检查
# 3. 缓存常用类型集合,避免重复创建
```bash
### 建议 B(高优先级):减少 builtins.len/isinstance/hasattr 调用
- *热点**:
- `len()`: 74.46M 次,18.05s
- `isinstance()`: 104.29M 次,14.21s
- `hasattr()`: 38.48M 次,8.46s
- *落地方向**:
1. **审查 LineBuffer/LineSeries 方法**,识别循环内的 `len()` 调用,提取到循环外
2. **替换 `isinstance(x, float)`**为 `type(x) is float`(对已知类型更快)
3.**替换 `hasattr(obj, attr)`**为 `getattr(obj, attr, None) is not None` 或 try/except
4.**缓存 `len(self)` 结果** 在迭代器的热路径中
- *示例修改**:
```python
# 前:
for i in range(len(self)):
if isinstance(val, float) and math.isnan(val):
...
# 后:
self_len = len(self)
float_type = float
isnan = math.isnan
for i in range(self_len):
val = ...
if type(val) is float_type and isnan(val):
...
```bash
### 建议 C(中优先级):优化 SMA.once() 滑动窗口
- *热点**: `sma.py:once` + `sma.py:<genexpr>` 合计 ~32.6s
- *问题**: 当前每个时间步都重新计算 `sum(src[i-period+1:i+1])` 生成器表达式。
- *落地方向**:
```python
def once(self, start, end):
src = self.data.array
dst = self.lines[0].array
period = self.params.period
# O(N) 滑动窗口
window_sum = sum(src[start:start+period])
dst[start + period - 1] = window_sum / period
for i in range(start + period, end):
window_sum += src[i] - src[i - period]
dst[i] = window_sum / period
```bash
### 建议 D(中优先级):优化 BollingerBands.once() 滑动窗口
- *热点**: `bollinger.py:once` - 22.07s (12 calls),单次调用 1.84s
- *问题**: 当前使用 O(N*period) 嵌套循环计算标准差。
- *落地方向**(恢复迭代 120 被回退的优化):
```python
def once(self, start, end):
# 使用 rolling sum 和 rolling sum_sq 实现 O(N) 计算
# 需要确保数值稳定性,避免浮点误差累积
pass
```bash
- *注意**: 此优化在迭代 120 中实现后被用户回退,需确认原因后决定是否重新引入。
### 建议 E(低优先级):LineBuffer.forward() 微优化
- *热点**: `linebuffer.py:599(forward)` - 10.73M 次,18.33s
- *落地方向**:
1. 减少方法内的属性访问,缓存 `self.array` 到局部变量
2. 考虑使用 `__slots__` 减少实例字典开销
3. 内联简单逻辑,减少函数调用开销
### 建议 F(评估项):autodict.__getattr__/__setattr__ 优化
- *热点**:
- `autodict.py:178(__getattr__)`: 8.76M 次,2.95s
- `autodict.py:184(__setattr__)`: 4.75M 次,1.89s
- *落地方向**:
- 减少 AutoDict 的使用场景,或提供更轻量的替代实现
- --
## 验证方案
### 安装验证
```bash
pip install -U .
```bash
### 格式化/质量检查
```bash
bash scripts/optimize_code.sh
```bash
### 单元测试
```bash
pytest tests -n 12
```bash
### 性能复测
```bash
python scripts/profile_performance.py --processes 12
```bash
### 对比基准(最新)
- master: `logs/performance_profile_master_20260117_093200.log` (**563.25s**)
- development: `logs/performance_profile_development_20260117_093551.log` (**554.07s**)
- **当前状态**: ✅ development 已比 master 快 1.6%
- **测试状态**: ✅ 478/478 测试通过
- --
## 优先级排序(更新)
| 优先级 | 建议 | 预期收益 | 实现难度 | 状态 |
|--------|------|----------|----------|------|
| ✅ 完成 | 核心优化(迭代 120) | ~150s | - | 已完成 |
| 🟡 可选 | A - LineSeries.__setattr__ 优化 | 5-10s | 中 | 待评估 |
| 🟡 可选 | B - 减少 len/isinstance/hasattr | 5-10s | 低-中 | 待评估 |
| 🟡 可选 | C - SMA.once() 滑动窗口 | 5-8s | 中 | 待评估(需确保测试兼容) |
| 🟡 可选 | D - BollingerBands.once() 滑动窗口 | 5-8s | 中 | 待评估(需确保测试兼容) |
| 🟢 低 | E - LineBuffer.forward() 微优化 | 2-3s | 低 | 可选 |
| 🟢 低 | F - autodict 优化 | 1-2s | 低 | 可选 |
- *当前成果**: development 性能已超越 master **1.6%**,核心优化目标已达成!
- --
## 结论
经过迭代 120 的优化工作,development 分支性能已经超越 master 分支:
- **性能提升**: 从 +28.8% 慢 → **快 1.6%**
- **测试通过率**: 478/478 (100%)
- **代码质量**: `scripts/optimize_code.sh` 验证通过
后续优化(建议 A-F)为**可选项**,可在确保测试兼容的前提下逐步实施。