Почему этот код очень медленный

Я начал эксперимент с ориентированным на данные дизайном. Я изначально начал делать какой-то уп-код и обнаружил, что он очень медленный, не знаю почему. Вот один пример:
У меня есть игровой объект

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

3

Решение

затем я создаю 1 000 000 объектов, используя new, а затем перебираю
вызов UpdateFoo ()

Там ваша проблема прямо там. Не выделяйте миллион маленьких вещей по отдельности, которые будут обрабатываться многократно с использованием универсального распределителя.

Попытайтесь хранить объекты непрерывно или в непрерывных кусках. Простое решение — хранить их в одном большом std::vector, Чтобы удалить в постоянное время, вы можете поменять элемент, чтобы удалить с последним и вспять. Если вам нужны стабильные индексы, вы можете оставить дыру, которая будет исправлена ​​при вставке (можно использовать свободный список или подход с использованием стека). Если вам нужны стабильные указатели, которые не делают недействительными, deque может быть вариантом в сочетании с идеей «дыр» с использованием свободного списка или отдельного стека индексов для восстановления / перезаписи.

Вы также можете просто использовать свободный распределитель списков и использовать новое размещение вместо него, при этом стараясь не использовать тот же распределитель и вручную вызывать dtor, но это усложняется быстрее и требует больше практики, чтобы преуспеть, чем подход со структурой данных. Вместо этого я рекомендую просто хранить свои игровые объекты в каком-то большом контейнере, чтобы вы вернули себе контроль над тем, где все будет находиться в памяти, и пространственной локализацией, которая получается.

Я тестировал на vs2008 и vs2010 в версии сборки, i7-4790 Любая идея, как
эта разница может случиться? Связано ли это с каким-либо связным кэшем?
поведение.

Если вы тестируете и правильно строите проект, возможно, распределитель фрагментирует память больше, когда GameObject меньше в тех случаях, когда вы получаете больше кеша в результате. Казалось бы, это наиболее вероятное объяснение, но трудно узнать наверняка без хорошего профилировщика.

Тем не менее, вместо того, чтобы анализировать его дальше, я рекомендую вышеупомянутое решение, чтобы вам не приходилось беспокоиться о том, где распределитель выделяет каждую маленькую вещь в памяти.

1

Другие решения

Других решений пока нет …