应改用连续存储的 std::vector 配合索引数组,提升缓存命中率与 simd 效率;指针随机分布导致频繁缓存未命中,而紧凑结构体+有序索引可预取、对齐、提前退出。

为什么直接用 std::vector<object></object> 做剔除会拖慢帧率
视锥体剔除在每帧都要遍历大量物体,如果用 std::vector<object></object> 存储待测对象,CPU 缓存命中率会很差——指针指向的 Object 实例在堆上随机分布,每次取 boundingBox 都可能触发一次缓存未命中。实测在 10K 物体场景下,单纯遍历指针数组就占去剔除总耗时的 40% 以上。
真正该优化的不是“要不要用指针”,而是“指针指向的数据布局是否利于批量访问”。常见错误是把“用指针 = 灵活”和“用指针 = 高效”混为一谈。
- 避免让
Object实例分散在new出来的零散内存块中 - 不要对每个
Object*单独调用getAABB()—— 这会打断向量化机会 - 若
Object本身含虚函数或非 POD 成员,sizeof(Object)不稳定,不利于结构体数组对齐
用 std::vector<object></object> + 索引数组替代指针数组
把所有 Object 实例连续存进一个 std::vector<object></object>,再用 std::vector<size_t></size_t>(或 uint32_t)存“活跃索引”,剔除时只遍历索引数组,按序访问 objects[i].aabb。这样 CPU 能预取、SIMD 可对齐、分支预测更准。
关键点在于:索引数组可排序(如按 Z 排序),剔除时还能提前退出;而原始指针数组无法保证访问局部性。
-
objects必须是 trivially copyable,且aabb是紧凑 struct(如struct AABB { vec3 min; vec3 max; };) - 索引数组大小应预留足够容量,避免频繁 realloc 导致迭代器失效
- 剔除后不 erasing
objects,只更新索引数组长度——删除成本从 O(N) 降到 O(1)
// 示例:剔除循环内访存模式 for (size_t i = 0; i <h3>当必须用指针时,如何让 <code>Object*</code> 保持缓存友好</h3> <p>某些场景确实无法避免指针(如多态渲染组件、运行时插拔系统)。这时要控制指针本身的布局,而非放任其野蛮生长。</p><div class="aritcle_card flexRow artxards"> <div class="artcardd flexRow"> <a class="aritcle_card_img" rel="nofollow" href="/xiazai/gongju/2823" title="C++14"><img src="https://img.php.cn/upload/manual/001/431/639/6ac8b33c327c4749.png" alt="C++14" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a> <div class="aritcle_card_info flexColumn"> <a rel="nofollow" href="/xiazai/gongju/2823" title="C++14" class="overflowclass">C++14</a> <p class="overflowclass">C++14 对 C++11 的修正与增强版本,适合旧系统维护和较老工具链兼容。</p> </div> <a rel="nofollow" href="/xiazai/gongju/2823" title="C++14" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span> </a> </div> </div>
- 用
std::vector<:unique_ptr>></:unique_ptr>替代裸指针容器——构造时配合reserve()+ 批量make_unique,能提升分配局部性 - 更优解是自定义 arena 分配器:所有
Object实例从一块大 buffer 中按需切出,保证物理相邻 - 禁止在剔除循环里通过
obj->getComponent<transform>()</transform>多次跳转——把常用字段(如 world matrix 的 frustum 相关部分)冗余到Object的 SOA 区域
注意:reinterpret_cast<char>(ptr)</char> 对齐检查不能省。若 Object 内部有 alignas(32) 成员,但分配器没对齐,AVX 加载会触发 EXCEPTION_DATATYPE_MISALIGNMENT。
std::span<const object></const> 如何安全暴露只读视图
剔除结果常需传给渲染线程,用 std::span 比裸指针 + size 更安全,且无额外开销。它不拥有数据,只提供带边界检查的视图(debug 模式下),编译期可推导长度。
重点是生命周期管理:剔除结果 span 的 lifetime 必须短于 objects vector 的 lifetime,否则悬空引用比裸指针更隐蔽。
- 返回
std::span<const object></const>时,确保底层 vector 不会在 span 使用期间 reallocate - 不要对 span 做
data() + offset手动算址——这绕过 span 边界检查,且破坏 aliasing 优化 - 若需跨线程传递,用
std::shared_ptr<:vector>></:vector>包裹数据,span 仅作栈上轻量视图
复杂点往往不在算法逻辑,而在内存布局与生命周期的耦合。一个 std::vector<object></object> 看似简单,但若没控制好对齐、没预分配、没分离剔除索引与实体存储,指针优化反而会让性能更差。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










