3

我开始了一些面向数据的设计实验。我最初开始做一些 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);
        }

完成循环大约需要 20 毫秒。当我注释掉float 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;
          }
     };

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

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

这是整个样本:

    #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]);
       //   }
     }
4

1 回答 1

1

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

你的问题就在那里。不要单独分配一百万个将使用通用分配器重复处理的小东西。

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

您也可以只使用空闲列表分配器并对其使用placement new,同时小心地使用相同的分配器释放并手动调用dtor,但这会变得更快,并且比数据结构方法需要更多的练习才能做好。我建议改为将游戏对象存储在某个大容器中,以便您重新控制所有内容将驻留在内存中的位置以及由此产生的空间位置。

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

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

也就是说,我建议使用上述解决方案,而不是进一步分析它,这样您就不必担心分配器在内存中分配每一个小东西的位置。

于 2017-12-21T01:04:50.840 回答