Я начал эксперимент с ориентированным на данные дизайном. Я изначально начал делать какой-то уп-код и обнаружил, что он очень медленный, не знаю почему. Вот один пример:
У меня есть игровой объект
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;
}
};
затем я создаю 1 000 000 объектов, используя new, а затем зацикливаюсь на вызове 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]);
// }
}
затем я создаю 1 000 000 объектов, используя new, а затем перебираю
вызов UpdateFoo ()
Там ваша проблема прямо там. Не выделяйте миллион маленьких вещей по отдельности, которые будут обрабатываться многократно с использованием универсального распределителя.
Попытайтесь хранить объекты непрерывно или в непрерывных кусках. Простое решение — хранить их в одном большом std::vector
, Чтобы удалить в постоянное время, вы можете поменять элемент, чтобы удалить с последним и вспять. Если вам нужны стабильные индексы, вы можете оставить дыру, которая будет исправлена при вставке (можно использовать свободный список или подход с использованием стека). Если вам нужны стабильные указатели, которые не делают недействительными, deque
может быть вариантом в сочетании с идеей «дыр» с использованием свободного списка или отдельного стека индексов для восстановления / перезаписи.
Вы также можете просто использовать свободный распределитель списков и использовать новое размещение вместо него, при этом стараясь не использовать тот же распределитель и вручную вызывать dtor, но это усложняется быстрее и требует больше практики, чтобы преуспеть, чем подход со структурой данных. Вместо этого я рекомендую просто хранить свои игровые объекты в каком-то большом контейнере, чтобы вы вернули себе контроль над тем, где все будет находиться в памяти, и пространственной локализацией, которая получается.
Я тестировал на vs2008 и vs2010 в версии сборки, i7-4790 Любая идея, как
эта разница может случиться? Связано ли это с каким-либо связным кэшем?
поведение.
Если вы тестируете и правильно строите проект, возможно, распределитель фрагментирует память больше, когда GameObject
меньше в тех случаях, когда вы получаете больше кеша в результате. Казалось бы, это наиболее вероятное объяснение, но трудно узнать наверняка без хорошего профилировщика.
Тем не менее, вместо того, чтобы анализировать его дальше, я рекомендую вышеупомянутое решение, чтобы вам не приходилось беспокоиться о том, где распределитель выделяет каждую маленькую вещь в памяти.
Других решений пока нет …