You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

面向数据设计:缓存友好迭代的数据准备与直接访问对比

数据导向设计中预处理数据的效率疑问解答

背景与示例代码

Mike Acton的相关演讲及技术博客均提到:在处理非连续数据时,先通过预处理将所需数据聚合为缓存友好的结构,再进行迭代计算的设计思路。以下是博客中的示例代码:

// objects_store.h
struct ObjectsStore
{
    vector<Vector3D> m_Positions;
    vector<Quaternion> m_Orientations;
};

// Runtime solution:
struct TranslationData
{
    Vector3D m_ObjPosition;
    const Quaternion m_ObjOrientation;
    const Vector3D m_ObjTranslation;
};

void PrepareTranslations(const ObjectsStore& inStore,
                         const vector<Vector3D>& inTranslations,
                         vector<TranslationData>& outObjectsToTranslate)
{
    assert(inStore.m_Positions.size() == inStore.m_Orientations.size());
    assert(inStore.m_Positions.size() == inTranslations.size());
    for (size_t i = 0; i < inTranslations.size(); ++i)
    {
        outObjectsToTranslate.push_back
        ({ 
             inStore.m_Positions[i],
             inStore.m_Orientations[i],
             inTranslations[i]
        });
    }
}

void TranslateObject(vector<TranslationData>& ioObjectsToTranslate)
{
    for (TranslationData& data: ioObjectsToTranslate)
        data.m_ObjPosition += data.m_ObjOrientation * data.m_ObjTranslation;
}

void ApplyTranslation(const vector<TranslationData>& inTranslationData,
                      ObjectsStore& outStore)
{
    assert(outStore.m_Positions.size() == inTranslationData.size());
    for (size_t i = 0; i < inTranslationData.size(); ++i)
        outStore.m_Positions[i] = inTranslationData[i].m_ObjPosition;
}

void UpdateGame(ObjectsStore& ioStore, vector<Vector3D>& inTranslations)
{
   vector<TranslationData> translation_data;
   PrepareTranslations(ioStore, inTranslations, translation_data);
   TranslateObject(translation_data);
   ApplyTranslation(translation_data, ioStore);
}

核心疑问

预处理数据需要额外的访问与复制开销,跳过这一步直接修改原始数据会不会更高效?这种设计到底是为了代码清晰等非性能收益,还是确实在性能上更有优势?

解答

1. 缓存命中率的量级优势

直接修改原始数据时,ObjectsStore采用**面向数据的存储(SOA)**结构:m_Positions、m_Orientations和inTranslations是三个独立向量,内存地址不连续。每次循环需要跨三个向量读取数据,会导致极高的缓存 miss 率——缓存 miss 的开销通常是数十到数百个CPU周期,远超过数据复制的开销。

而预处理后的TranslationData是**面向对象的存储(AOS)**结构:每个对象的所有计算相关数据连续存放,迭代时能一次性加载到缓存行中,缓存命中率会大幅提升。尤其是当TranslateObject中的计算逻辑越复杂,缓存友好带来的性能收益就越能覆盖预处理的成本。

2. 指令优化与CPU流水线利用

连续存储的数据能充分发挥CPU的预取机制:预取器可以准确预测下一批要访问的数据,提前加载到缓存,避免CPU流水线停顿。同时,编译器更容易对连续数据进行向量化优化(SIMD指令),一次性处理多个元素,进一步提升计算效率。而访问分散的向量时,编译器几乎无法进行这类优化,CPU的算力会被浪费。

3. 预处理成本的摊平与优化

如果该计算逻辑是游戏循环中的高频操作(比如每帧执行),单次预处理的成本会被多次高效计算摊平。此外,还可以提前为TranslationData向量预留足够容量,避免每次push_back的内存分配开销,进一步降低预处理的额外成本。

4. 代码模块化的额外收益

这种拆分确实提升了代码的可维护性:PrepareTranslations专注数据聚合、TranslateObject专注核心计算、ApplyTranslation负责结果回写,职责单一,逻辑清晰。后续修改计算逻辑时,只需改动TranslateObject,不会影响数据读取和回写的逻辑,降低了维护成本。


内容的提问来源于stack exchange,提问作者SinixND

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 01:47:35