为什么这段代码非常慢?任何与缓存行为有关的事情?

时间:2017-08-14 09:51:35

标签: c++ data-oriented-design

我开始进行一些面向数据的设计实验。我最初开始做一些oop代码,发现一些代码非常慢,不知道为什么。这是一个例子: 我有一个游戏对象

    class GameObject
    {
    public:
          float m_Pos[2];
          float m_Vel[2];
          float m_Foo;

          void UpdateFoo(float f){
             float mag = sqrtf(m_Vel[0] * m_Vel[0] + m_Vel[1] * m_Vel[1]);
             m_Foo += mag * f;
          }
     };

然后我使用new创建1,000,000个对象,然后循环调用UpdateFoo()

        for (unsigned i=0; i<OBJECT_NUM; ++i)
        {
           v_objects[i]->UpdateFoo(10.0);
        }

完成循环大约需要20ms。当我注释掉浮点m_Pos [2]时发生了奇怪的事情,所以对象看起来像这样

    class GameObject
    {
    public:
          //float m_Pos[2];
          float m_Vel[2];
          float m_Foo;

          void UpdateFoo(float f){
             float mag = sqrtf(m_Vel[0] * m_Vel[0] + m_Vel[1] * m_Vel[1]);
             m_Foo += mag * f;
          }
     };

然后突然循环需要大约150ms才能完成。如果我把任何东西放在m_Vel之前,要快得多。我尝试在m_Vel和m_Foo之间或m_Vel之前的其他地方放一些填充....慢。

我在发布版本i7-4790上对vs2008和vs2010进行了测试 知道如何发生这种差异吗?它与任何缓存一致行为有关。

这里是整个样本:

    #include <iostream>
    #include <math.h>
    #include <vector>
    #include <Windows.h>

    using namespace std;

    class GameObject
    {
    public:
        //float m_Pos[2];
        float m_Velocity[2];
        float m_Foo;

        void UpdateFoo(float f)
        {
          float mag = sqrtf(m_Velocity[0] * m_Velocity[0] + m_Velocity[1] * 
                            m_Velocity[1]);
          m_Foo += mag * f;
         }
    };



     #define OBJECT_NUM 1000000

     int main(int argc, char **argv)
     {
       vector<GameObject*> v_objects;
       for (unsigned i=0; i<OBJECT_NUM; ++i)
       {
          GameObject * pObject = new GameObject;
          v_objects.push_back(pObject);
       }

       LARGE_INTEGER nFreq;
       LARGE_INTEGER nBeginTime;
       LARGE_INTEGER nEndTime;
       QueryPerformanceFrequency(&nFreq);
       QueryPerformanceCounter(&nBeginTime);

       for (unsigned i=0; i<OBJECT_NUM; ++i)
       {
           v_objects[i]->UpdateFoo(10.0);
       }

       QueryPerformanceCounter(&nEndTime);
       double dWasteTime = (double)(nEndTime.QuadPart-
                       nBeginTime.QuadPart)/(double)nFreq.QuadPart*1000;

       printf("finished: %f", dWasteTime);

       //   for (unsigned i=0; i<OBJECT_NUM; ++i)
       //   {
       //       delete(v_objects[i]);
       //   }
     }

1 个答案:

答案 0 :(得分:1)

  

然后我使用new创建1,000,000个对象,然后循环   调用UpdateFoo()

那里有你的问题。不要分别使用通用分配器重复分配一百万个小东西。

尝试连续或以连续的块存储对象。一个简单的解决方案是将它们全部存储在一个大std::vector中。要在恒定时间内删除,您可以将元素交换为最后一个并弹回。如果你需要稳定的索引,你可以在插入后留下一个洞来回收(可以使用免费列表或堆栈方法)。如果你需要不会失效的稳定指针,deque可能是一个选项,结合“漏洞”的想法,使用一个空闲列表或单独的索引堆栈来回收/覆盖。

你也可以使用一个空闲列表分配器,并使用新的贴图,同时小心释放使用相同的分配器并手动调用dtor,但这会更加混乱,需要更多的练习才能比数据结构方法更好。我建议只是试图将你的游戏对象存储在一个大容器中,这样你就可以控制所有内容将留在内存中的位置以及产生的空间位置。

  

我在版本构建中对vs2008和vs2010进行了测试,i7-4790任何想法如何   这种差异可能发生?它是否与任何缓存相关   行为。

如果您正在进行基准测试并正确构建项目,那么当GameObject较小时,分配器可能会更多地分割内存,从而导致更多的缓存未命中。这似乎是最可能的解释,但如果没有一个好的剖析器,很难确定。

那就是说,我没有进一步分析,而是推荐上面的解决方案,这样你就不用担心分配器在内存中分配每一件小东西了。