216 lines
5.4 KiB
Markdown
216 lines
5.4 KiB
Markdown
# 内存优化分析与修复方案
|
||
|
||
## 问题分析
|
||
|
||
基于 `/sw-debug.html` 的内存日志分析,发现以下问题:
|
||
|
||
### 内存使用情况
|
||
|
||
| 指标 | 值 | 问题 |
|
||
|------|-----|------|
|
||
| 已使用内存 | 3.3-4.3 GB | 🔴 极高 |
|
||
| 内存上限 | 4 GB | 接近/超过上限 |
|
||
| 使用率 | 77-101% | 🔴 危险区域 |
|
||
| 图片数量 | 30 | 可能过多 |
|
||
| 视频数量 | 1 | - |
|
||
| Plait 元素 | 80 | 正常 |
|
||
|
||
### 关键发现
|
||
|
||
1. **启动时就有 ~3.5GB 内存** - 说明不是运行时泄漏,而是初始加载问题
|
||
2. **长任务期间内存飙升到 4.3GB** - 超过了 4GB 限制
|
||
3. **刷新后内存不降反升** - 每次刷新内存都在增长
|
||
|
||
## 问题根源
|
||
|
||
### 1. 高分辨率图片解码 (主要问题)
|
||
|
||
**问题描述**:
|
||
- 30 张图片在画布上全部解码到内存中
|
||
- 一张 4K 图片 (4096x2160) 解码后约 35MB
|
||
- 30 张高分辨率图片可能占用 300-1000MB
|
||
|
||
**代码位置**:
|
||
- `packages/drawnix/src/plugins/with-image.tsx`
|
||
- Plait 框架内部的图片渲染
|
||
|
||
### 2. 聊天附件 Base64 存储 (中等问题)
|
||
|
||
**问题描述**:
|
||
- 聊天消息的附件以 base64 字符串形式存储在 IndexedDB
|
||
- 一张 2MB 的图片转为 base64 后约 2.6MB
|
||
- 大量附件累积会占用大量存储和内存
|
||
|
||
**代码位置**:
|
||
```typescript
|
||
// packages/drawnix/src/types/chat.types.ts
|
||
export interface Attachment {
|
||
data: string; // Base64 编码,可能非常大
|
||
}
|
||
```
|
||
|
||
### 3. 任务数据累积
|
||
|
||
**问题描述**:
|
||
- 已完成的任务永久保存在 IndexedDB
|
||
- 任务结果包含 URL 引用,但工作流数据可能较大
|
||
|
||
**代码位置**:
|
||
- `apps/web/src/sw/task-queue/storage.ts`
|
||
|
||
### 4. 视频解码缓冲区
|
||
|
||
**问题描述**:
|
||
- 视频元素的解码缓冲区占用内存
|
||
- 即使视频暂停,缓冲区也可能保留
|
||
|
||
## 已实施的修复
|
||
|
||
### 1. 内存监控服务
|
||
|
||
添加了 `memory-monitor-service.ts`:
|
||
|
||
```typescript
|
||
// packages/drawnix/src/services/memory-monitor-service.ts
|
||
|
||
// 功能:
|
||
// - 周期性检查内存使用(每 30 秒)
|
||
// - 在内存压力过大时触发清理(75% 阈值)
|
||
// - 支持注册自定义清理处理器
|
||
// - 提供内存统计 API
|
||
|
||
// 使用:
|
||
import { memoryMonitorService } from './services/memory-monitor-service';
|
||
|
||
// 获取内存状态
|
||
const stats = memoryMonitorService.getMemoryStats();
|
||
console.log(stats?.formatted.used); // "3.2 GB"
|
||
|
||
// 手动触发清理
|
||
await memoryMonitorService.triggerCleanup();
|
||
```
|
||
|
||
### 2. 自动启动内存监控
|
||
|
||
在 `apps/web/src/main.tsx` 中添加了自动启动:
|
||
|
||
```typescript
|
||
import { memoryMonitorService } from '../../../packages/drawnix/src/services/memory-monitor-service';
|
||
|
||
// 延迟 5 秒启动,避免影响首屏加载
|
||
setTimeout(() => {
|
||
memoryMonitorService.start();
|
||
memoryMonitorService.logMemoryStatus();
|
||
}, 5000);
|
||
```
|
||
|
||
### 3. 离屏图片自动卸载
|
||
|
||
内存监控服务包含了离屏图片清理逻辑:
|
||
|
||
```typescript
|
||
// 找到离屏图片并暂时移除 src
|
||
const isOffscreen =
|
||
rect.bottom < -500 ||
|
||
rect.top > window.innerHeight + 500;
|
||
|
||
if (isOffscreen) {
|
||
img.setAttribute('data-lazy-src', src);
|
||
img.removeAttribute('src');
|
||
}
|
||
```
|
||
|
||
## 建议的进一步优化
|
||
|
||
### 优先级 1:图片懒加载优化
|
||
|
||
**目标**:只解码视口内的图片
|
||
|
||
**方案**:
|
||
1. 在 Plait 的图片渲染中使用 `loading="lazy"` 和 `decoding="async"`
|
||
2. 对离屏图片使用占位符而不是实际图片
|
||
|
||
**修改位置**:
|
||
- `packages/drawnix/src/plugins/with-image.tsx`
|
||
|
||
### 优先级 2:聊天附件外部化存储
|
||
|
||
**目标**:减少 IndexedDB 中的 base64 数据
|
||
|
||
**方案**:
|
||
1. 将附件 Blob 存储到 Cache API
|
||
2. 在 IndexedDB 中只存储虚拟 URL 引用
|
||
3. 需要时从 Cache API 获取
|
||
|
||
**修改位置**:
|
||
- `packages/drawnix/src/services/chat-storage-service.ts`
|
||
- `packages/drawnix/src/types/chat.types.ts`
|
||
|
||
### 优先级 3:图片尺寸限制
|
||
|
||
**目标**:限制画布上图片的最大解码尺寸
|
||
|
||
**方案**:
|
||
1. 在插入图片时检查尺寸
|
||
2. 对超大图片自动缩放
|
||
3. 提供高清预览选项
|
||
|
||
### 优先级 4:定期内存报告
|
||
|
||
**目标**:帮助用户了解内存使用情况
|
||
|
||
**方案**:
|
||
1. 在设置页面显示内存使用统计
|
||
2. 在内存压力时显示警告
|
||
3. 提供手动清理选项
|
||
|
||
## 用户操作建议
|
||
|
||
如果遇到内存问题,可以:
|
||
|
||
1. **减少画布上的图片数量** - 将不需要的图片移到其他画板
|
||
2. **使用较低分辨率的图片** - 特别是在移动设备上
|
||
3. **定期刷新页面** - 释放累积的内存
|
||
4. **清理聊天历史** - 删除包含大量附件的旧会话
|
||
5. **关闭其他标签页** - 释放浏览器资源
|
||
|
||
## 调试方法
|
||
|
||
### 使用 SW Debug 面板
|
||
|
||
访问 `/sw-debug.html` 查看:
|
||
- 内存使用趋势
|
||
- 长任务监控
|
||
- 内存快照
|
||
|
||
### 使用 Chrome DevTools
|
||
|
||
1. **Memory 面板**:
|
||
- 拍摄堆快照分析内存分布
|
||
- 查看哪些对象占用最多内存
|
||
|
||
2. **Performance 面板**:
|
||
- 记录性能profile
|
||
- 分析长任务和内存增长
|
||
|
||
### 控制台命令
|
||
|
||
```javascript
|
||
// 获取内存状态
|
||
memoryMonitorService.logMemoryStatus();
|
||
|
||
// 手动触发清理
|
||
await memoryMonitorService.triggerCleanup(true);
|
||
|
||
// 获取详细统计
|
||
const stats = memoryMonitorService.getMemoryStats();
|
||
console.table(stats);
|
||
```
|
||
|
||
## 相关文件
|
||
|
||
- `packages/drawnix/src/services/memory-monitor-service.ts` - 内存监控服务
|
||
- `apps/web/src/main.tsx` - 内存监控初始化
|
||
- `packages/drawnix/src/components/lazy-image/LazyImage.tsx` - 懒加载图片组件
|
||
- `packages/drawnix/src/hooks/useImageLazyLoad.ts` - 图片懒加载 Hook
|