迭代 126 - 性能优化分析报告¶
1. 性能测试概况¶
测试环境¶
测试脚本:
scripts/profile_performance.py测试策略数量: 119 个
成功率: 100% (119/119)
执行时间对比¶
| 分支 | 总执行时间 | 日期 |
|——|———–|——|
| Master 基准 | 563.25 秒 | 2026-01-17 09:32 |
| Development | 348.33 秒 | 2026-01-17 13:47 |
性能提升*: Development 分支比 Master 快约 38%(减少 214.92 秒)
2. 热点函数分析¶
2.1 按累计时间排序的 TOP 20 函数¶
| 排名 | 函数 | 调用次数 | tottime(秒) | cumtime(秒) | 优化潜力 |
|——|——|———-|————-|————-|———-|
| 1 | cerebro.py:_runonce | 117 | 4.03 | 216.70 | 低 |
| 2 | strategy.py:_oncepost | 685,002 | 6.02 | 110.70 | 中 |
| 3 | feed.py:load | 920,363 | 3.21 | 70.21 | 中 |
| 4 | feed.py:advance | 984,017 | 2.09 | 44.87 | 中 |
| 5 | feed.py:preload | 91 | 0.05 | 44.60 | 低 |
| 6 | strategy.py:_next_analyzers | 688,411 | 2.21 | 32.57 | 高|
| 7 | cerebro.py:_brokernotify | 688,528 | 0.93 | 32.46 | 中 |
| 8 | brokers/bbroker.py:next | 688,528 | 4.82 | 29.91 | 中 |
| 9 | lineseries.py:forward | 2,100,081 | 2.96 | 26.74 |高|
| 10 | feed.py:_load | 655,493 | 0.77 | 27.14 | 低 |
| 11 | csvgeneric.py:_loadline | 618,940 | 6.47 | 24.92 |高|
| 12 | analyzer.py:_next | 2,357,296 | 1.46 | 24.18 |高|
| 13 | linebuffer.py:forward | 10,730,236 | 16.09 | 23.78 |高|
| 14 | feed.py:_tick_fill | 985,770 | 6.31 | 21.04 |高|
| 15 | strategy.py:_notify | 688,411 | 6.53 | 19.12 |高|
| 16 | lineiterator.py:_once | 117 | 0.01 | 19.08 | 低 |
| 17 | lineseries.py:__setattr__ | 21,271,573 | 14.53 | 16.20 |高|
| 18 | brokers/bbroker.py:_get_value | 711,749 | 4.23 | 14.52 | 中 |
| 19 | analyzers/drawdown.py:next | 687,646 | 6.32 | 12.17 |高|
| 20 | lineseries.py:advance | 2,551,428 | 2.83 | 11.97 |高 |
2.2 按自身时间排序的 TOP 15 函数¶
| 排名 | 函数 | 调用次数 | tottime(秒) | 优化建议 |
|——|——|———-|————-|———-|
| 1 | linebuffer.py:forward | 10,730,236 | 16.09 | 减少 dict 访问 |
| 2 | lineseries.py:__setattr__ | 21,271,573 | 14.53 | 简化类型检查 |
| 3 | linebuffer.py:__setitem__ | 10,677,205 | 8.37 | 预计算标志位 |
| 4 | linebuffer.py:__getitem__ | 19,049,698 | 7.40 | 移除异常处理 |
| 5 | builtins.len | 39,721,284 | 7.36 | 缓存长度值 |
| 6 | dateintern.py:num2date | 2,227,017 | 7.21 | 批量转换 |
| 7 | indicators/bollinger.py:once | 12 | 6.91 | numpy 向量化 |
| 8 | builtins.getattr | 31,960,177 | 6.89 | 直接属性访问 |
| 9 | strategy.py:_notify | 688,411 | 6.53 | 减少 chain 开销 |
| 10 | csvgeneric.py:_loadline | 618,940 | 6.47 | 批量解析 |
| 11 | analyzers/drawdown.py:next | 687,646 | 6.32 | 简化计算 |
| 12 | feed.py:_tick_fill | 985,770 | 6.31 | 批量 setattr |
| 13 | strategy.py:_oncepost | 685,002 | 6.02 | 减少 hasattr |
| 14 | linebuffer.py:advance | 9,123,779 | 5.81 | 内联简化 |
| 15 | linebuffer.py:set_idx | 23,273,792 | 5.23 | 直接赋值 |
3. 具体优化建议¶
3.1 高优先级优化 (预估提升 15-25%)¶
3.1.1 linebuffer.py:forward() - 16.09 秒¶
当前代码问题*:
def forward(self, value=float("nan"), size=1):
self_dict = self.__dict__
is_indicator = self_dict.get("_is_indicator", False)
# ... 多次 self_dict.get() 调用
```bash
- *优化建议**:
```python
def forward(self, value=float("nan"), size=1):
# 直接使用实例属性,避免__dict__访问
is_indicator = self._is_indicator # 假设__init__中已初始化
# NaN 检查优化
if value is None or value != value:
value = float("nan") if is_indicator else 0.0
# 减少条件分支
self.idx += size
self.lencount += size
# 使用 array.extend 替代循环 append
if size == 1:
self.array.append(value)
else:
self.array.extend([value] *size)
```bash
- *预估提升**: 3-5 秒
- --
#### 3.1.2 `lineseries.py:__setattr__()` - 14.53 秒
- *当前代码问题**:
- 每次属性设置都进行多次类型检查
- 使用`type(value)`和多重条件判断
- *优化建议**:
```python
# 使用__slots__减少属性查找开销
__slots__ = ('lines', 'datas', '_indicators', ...)
def __setattr__(self, name, value):
# 快速路径:下划线开头直接设置
if name[0] == '_':
object.__setattr__(self, name, value)
return
# 使用预计算的类型集合
if type(value) in _SIMPLE_TYPES_CACHED: # 模块级缓存
object.__setattr__(self, name, value)
return
# 其他情况...
```bash
- *预估提升**: 4-6 秒
- --
#### 3.1.3 `strategy.py:_notify()` - 6.53 秒
- *当前代码问题**:
```python
for analyzer in itertools.chain(self.analyzers, self._slave_analyzers):
analyzer._notify_cashvalue(cash, value)
analyzer._notify_fund(cash, value, fundvalue, fundshares)
```bash
- *优化建议**:
```python
def _notify(self, qorders=[], qtrades=[]):
# 预合并 analyzer 列表,避免每次 chain
if not hasattr(self, '_all_analyzers'):
self._all_analyzers = list(self.analyzers) + list(self._slave_analyzers)
# 批量通知
for analyzer in self._all_analyzers:
analyzer._notify_cashvalue(cash, value)
analyzer._notify_fund(cash, value, fundvalue, fundshares)
```bash
- *预估提升**: 2-3 秒
- --
#### 3.1.4 `feed.py:_tick_fill()` - 6.31 秒
- *当前代码问题**:
```python
def _tick_fill(self, force=False):
for lalias in self.getlinealiases():
if lalias != "datetime":
setattr(self, "tick_" + lalias, getattr(self.lines, lalias)[0])
```bash
- *优化建议**:
```python
def _tick_fill(self, force=False):
# 缓存 tick 属性名和 line 引用
if not hasattr(self, '_tick_cache'):
self._tick_cache = [
('tick_' + alias, getattr(self.lines, alias))
for alias in self.getlinealiases() if alias != "datetime"
]
for tick_name, line in self._tick_cache:
setattr(self, tick_name, line[0])
```bash
- *预估提升**: 2-3 秒
- --
### 3.2 中优先级优化 (预估提升 10-15%)
#### 3.2.1 `indicators/bollinger.py:once()` - 6.91 秒
- *优化建议**: 使用 NumPy 向量化计算
```python
def once(self, start, end):
import numpy as np
darray = np.array(self.data.array)
period = self.p.period
devfactor = self.p.devfactor
# 使用滚动窗口计算
rolling_mean = np.convolve(darray, np.ones(period)/period, mode='valid')
rolling_std = np.array([np.std(darray[i:i+period]) for i in range(len(darray)-period+1)])
# 批量赋值
self.lines.mid.array[period-1:] = rolling_mean
self.lines.top.array[period-1:] = rolling_mean + devfactor *rolling_std
self.lines.bot.array[period-1:] = rolling_mean - devfactor*rolling_std
```bash
- *预估提升**: 4-5 秒
- --
#### 3.2.2 `parameters.py:get()` - 4.90 秒
- *优化建议**:
```python
def get(self, name: str, default: Any = None) -> Any:
# 单次 dict 查找优化
try:
return self._values[name]
except KeyError:
pass
try:
return self._value_cache[name]
except KeyError:
pass
# 缓存 descriptor 默认值
desc = self._descriptors.get(name)
if desc is not None:
val = desc.default
self._value_cache[name] = val
return val
return default
```bash
- *预估提升**: 1-2 秒
- --
#### 3.2.3 `analyzers/drawdown.py:next()` - 6.32 秒
- *当前实现可能存在重复计算**,建议:
- 缓存前一次的 peak value
- 使用增量更新而非每次重新计算
- --
### 3.3 低优先级优化 (预估提升 5-10%)
| 函数 | 优化方向 |
|------|----------|
| `linebuffer.py:__getitem__` | 移除 slow path 的 getattr 调用 |
| `linebuffer.py:advance` | 内联 idx 和 lencount 更新 |
| `dateintern.py:num2date` | 使用 LRU 缓存常用日期转换 |
| `metabase.py:p` | 缓存参数访问结果 |
| `autodict.py:__getattr__` | 减少字符串操作 |
- --
## 4. 内置函数调用优化
### 4.1 `builtins.len` - 39,721,284 次调用
- *优化策略**:
- 缓存频繁访问对象的长度
- 使用`lencount`属性替代重复的`len()`调用
- *示例**:
```python
# 优化前
for i in range(len(self.datas)):
if len(self.datas[i]) > 0:
...
# 优化后
datas = self.datas
datas_len = len(datas)
for i in range(datas_len):
if datas[i].lencount > 0:
...
```bash
### 4.2 `builtins.getattr` - 31,960,177 次调用
- *优化策略**:
- 使用`__dict__`直接访问
- 预计算属性引用
### 4.3 `builtins.hasattr` - 18,739,402 次调用
- *优化策略**:
- 使用 try/except 替代 hasattr
- 在`__init__`中确保所有属性都已初始化
- --
## 5. 数据加载优化
### 5.1 Pandas 数据源优化
- *当前状态**: `pandafeed.py:_load` 已优化(从 184.93 秒降至 8.34 秒)
- *进一步优化建议**:
- 使用`pandas.read_csv`的`dtype`参数预定义类型
- 考虑使用`pyarrow`或`polars`替代 pandas
### 5.2 CSV 数据源优化
`csvgeneric.py:_loadline` 消耗 6.47 秒
- *优化建议**:
- 批量读取和解析
- 使用`csv.reader`替代手动解析
- 预编译日期解析格式
- --
## 6. 实施优先级
### Phase 1 (立即实施) - 预估提升 10-15%
1. ✅ `linebuffer.py:forward()` 优化
2. ✅ `lineseries.py:__setattr__()` 简化
3. ✅ `strategy.py:_notify()` 预合并列表
### Phase 2 (短期实施) - 预估提升 8-12%
1. `feed.py:_tick_fill()` 缓存优化
2. `indicators/bollinger.py:once()` 向量化
3. `parameters.py:get()` 简化查找
### Phase 3 (中期实施) - 预估提升 5-8%
1. 减少`len()`和`getattr()`调用
2. `analyzers/drawdown.py:next()` 增量计算
3. `dateintern.py:num2date()` LRU 缓存
- --
## 7. 测试验证
每次优化后需要运行:
```bash
# 1. 安装更新并运行测试
pip install -U . && pytest tests -n 8
# 2. 代码格式检查
./scripts/optimize_code.sh
# 3. 性能测试
python scripts/profile_performance.py
```bash
- --
## 8. 总结
当前 Development 分支相比 Master 已有 38%的性能提升。通过本报告识别的优化点,预计还可以额外提升 **20-30%**的性能,主要集中在:
1.**热路径函数优化**: `linebuffer.forward`, `lineseries.__setattr__`
1. **减少 Python 开销**: 缓存属性访问,避免重复计算
2. **向量化计算**: 指标计算使用 NumPy
3. **批量操作**: 减少循环中的函数调用
- --
- 报告生成时间: 2026-01-17*
- 分析基于: performance_profile_development_20260117_134722.log*