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

Why this code extremely slow?Any thing related to cache behavior?

本文关键字:缓存 任何 段代码 代码 为什么 非常      更新时间:2023-10-16

我开始了一些面向数据的设计实验。我最初开始做一些 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之前的地方......慢。

我在发布版本i2008-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]);
//   }
}

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

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

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

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

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

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

也就是说,与其进一步分析,我推荐上面的解决方案,这样你就不必担心分配器在哪里分配内存中的每一个微小的东西。