> **本文回答**：批量 `addEntity` / `setData` 卡顿 —— 没合并 dirty / 没 bulk。

## 一、症状

- 循环 addEntity 1 万条 → 页面卡死 3~5 秒
- 循环改 GeoJSON feature → FPS 跌到 5
- 改颜色改透明度 → 每次都有感明显卡

## 二、根因：每条操作都触发一次重渲染

```js
for (const d of data) {
  mapcadLayer.addEntity(new vjmapext.PointEnt(d));
  // 每次 addEntity：标脏 + 写空间索引 + 发 entity:added 事件
}
```

注意 RAF 合并保证**渲染只发生一次**——1 万次循环的真实开销在 JS 侧（实体构造、`toGeoJSON` 编码、空间索引插入），不是 1 万次渲染。卡的根因是**单次循环量大**，不是"没合并渲染"。

## 三、解法：直接循环（无 bulk API，RAF 自动合并）

```js
// ✅ 直接循环即可——addEntity 只标脏，重绘按 requestAnimationFrame 合并到下一帧
for (const d of data) {
  mapcadLayer.addEntity(new vjmapext.PointEnt(d));
}
```

要点：循环内**不要穿插 await**（会把添加摊到多帧）；要一步撤销可包 `undoManager.beginTransaction()/endTransaction()`。

## 四、GeoJSON 批量

```js
// ❌
for (const f of newFeatures) {
  fc.features.push(f);
  src.setData(fc);        // 每次重排索引
}

// ✅
fc.features.push(...newFeatures);
src.setData(fc);          // 一次
```

## 五、大量样式变化（只改颜色）

```js
// ❌ 慢：changed() 全量
for (const e of ents) {
  e.color = 0xff0000;
  e.changed();
}

// ✅ 快：styleChanged()
for (const e of ents) {
  e.color = 0xff0000;
  e.styleChanged();
}
```

## 六、FX 批量

```js
// 两者性能相当：都只写 CPU 影子缓冲，下一帧合并成一次 GPU 上传
for (const p of points) fx.addPoint('fire', { cad: p, size: 30 });
fx.addPoints('fire', points.map(p => ({ cad: p, size: 30 })));   // 写法更简洁
```

FX 卡顿多半不是"逐个 add"造成的，而是实例数/预设档位过高：看 `fx.getMetrics()` 的 live 数，降 `fx.setQuality('lite')`，海量同形点改 `fx.symbolImage`。

## 七、流式加载（不阻塞 UI）

1000 条还 OK，10 万条 bulk 也可能让 UI 卡 1~2 秒。分批：

```js
const BATCH = 500;
let idx = 0;

function loadBatch() {
  const end = Math.min(idx + BATCH, data.length);
  for (; idx < end; idx++) {
    mapcadLayer.addEntity(toEnt(data[idx]));
  }
  if (idx < data.length) requestAnimationFrame(loadBatch);
}
loadBatch();
```

每帧 500 条，用户看到数据"流入"而非一卡。

## 八、Web Worker 预处理

大批量 CSV/JSON 解析放 Worker：

```js
// worker.js
self.onmessage = e => {
  const text = e.data;
  const ents = parseCSV(text).map(r => ({ type: 'Point', coords: [r.x, r.y] }));
  self.postMessage(ents);
};

// main
const w = new Worker('worker.js');
w.postMessage(csvText);
w.onmessage = e => {
  for (const d of e.data) mapcadLayer.addEntity(toEnt(d));
};
```

## 九、诊断流程

1. F12 → Performance → 录制 bulk 加载过程
2. 看主线程火焰图里最大的块
3. 如果是 `rebuild bucket` / `buffer upload` 多次 → 检查是不是每帧都在改实体
4. 如果是 `encode / setData` 多次 → 没合并
5. 如果是 `parseJSON / forEach` → 考虑 Worker

## 十、和 dynamic 的关系

批量添加和 dynamic 解决不同问题：

- 一次加载 1 万静态实体 → 直接循环 addEntity（RAF 合并渲染）+ **不** dynamic
- 持续改 100 条位置 → dynamic

## 十一、常见坑

1. **循环里穿插 await**：把添加摊到多帧、拉长总耗时；同步循环一把加完。
2. **循环 await 大量异步**：每条都 await 网络 → 串行非常慢；用 `Promise.all` 并发。
3. **GeoJSON source 频繁 setData**：source.setData 每次重排索引；先合并成完整 FeatureCollection 再设。
4. **改了 100 条几何 + 100 条只改色**：分开处理，几何用 `changed()`，样式用 `styleChanged()`。

## 相关

- 渲染桶 → [perf/render-buckets](../perf/render-buckets.md)
- 批量策略 → [perf/batch-rendering](../perf/batch-rendering.md)
- 实体批量 → [batch/entity-bulk-add](../batch/entity-bulk-add.md)
- GeoJSON 批量 → [batch/geojson-bulk](../../../vjmap/references/batch/geojson-bulk.md)
