Zloten
> А зачем его префетчить? если он ВОЗМОЖНО не понадобиться?
Так ведь весь смысл в том, чтобы он понадобился!
Dampire
> Я уже переделал под инверсию/прямую. Есть 2 флага и 2 функции апдейта матрицы.
> Хранить локальную и глобальную я не хочу, потому-что я пока не вижу примеров
> использования локальной матрицы. Позиция/вращение/размер у меня всегда
> относительно родителя, матрица же всегда мировая.
Банально, ты ставишь объект на платформу( лифт к примеру), и тебе нужно чтобы объект ехал вместе с лифтом, твои действия?
вторая причина, имея только одну матрицу поверни объект вокруг своей оси. даже нет не так. у тебя родитель вращается в одну сторону, а потомок в другую.
bazhenovc
> 2. Если dirty флаг даёт серьёзный прирост, может попробовать
> переписать/оптимизировать математику?
Ну как бы не об этом ли я в нескольких постах сказал?:-D
bazhenovc
> Применение data driven подхода будет давать прирост перформанса в любом случае,
> независимо от количества умножений.
А кто спорит? Если ты разом апдейтишь блок, то это конечно выгоднее, чем делать это по одиночке, но, не ты ли говорил, прежде чем делать такие оптимизации нужно очень хорошо знать работу кеша:-)
Стас
> Ну как бы не об этом ли я в нескольких постах сказал?:-D
Ну да.
Стас
> А кто спорит? Если ты разом апдейтишь блок, то это конечно выгоднее, чем делать
> это по одиночке, но, не ты ли говорил, прежде чем делать такие оптимизации
> нужно очень хорошо знать работу кеша:-)
Снова да.
Я просто хотел, чтобы автор научился писать кеш-френдли код. Но автор не хочет :(
bazhenovc
> Но автор не хочет :(
Может потому что предлагаемая тобой оптимизация, далеко не так очевидна, и ее не возможно отследить в тепличных условиях, даже твой тест, в тепличных условиях дал прирост от dirty флага.
Эта тема очень скользкая, я согласен что одно умножение матриц можно еще не скипать. Но 2 или 3? Ты уже не оправдаешь что лечишь кеш мисс. А инверсия, это вообще жесткий изврат, одной операции инверсии достаточно чтобы засунуть ее под флаг.
Стас
> Может потому что предлагаемая тобой оптимизация, далеко не так очевидна, и ее
> не возможно отследить в тепличных условиях, даже твой тест, в тепличных
> условиях дал прирост от dirty флага.
>Какие из этого можно сделать выводы?
>1. Data driven в 2 раза эффективнее, чем object-oriended с dirty флагом
>2. Test_DO_dirty просчитывает в 2 раза _меньше_ матриц, чем Test_DO_no_dirty, при этом по времени выполнения они почти не отличаются. То есть стоимость кэш-мисса == 2 апдейта матриц.
Почему все такие невнимательные? :( Я как раз показал, что dirty флаг НЕ дал прироста.
bazhenovc
> Почему все такие невнимательные? :( Я как раз показал, что dirty флаг НЕ дал
> прироста.
Test_OO_no_dirty: 1.13998
Test_OO_dirty: 0.629987
двухкратный прирост.
Test_DO_dirty: 0.249995
Test_DO_no_dirty: 0.269995
Прирост не значительный, но он есть даже в тепличных условиях.
Так что что конкретно ты показал?
Оба ваших теста не учитывают одну простую вещь, что в реальной ситуации у вас не будет 100% использования флага dirty. И в этих условиях уже может быть прирост, поскольку dirty
флаг дает лишь не значительный буст, его исчезновение, может принести прирост, но это ни как не проверить в тепличных условиях.
А причем здесь кешмиссы? Основные тормоза от таких флагов возникают из-за branch-misprediction & pipeline stall.
Кстати, советую проверить, что будет, если в конструкторах вместо
сделать
{
static bool prev = true;
dirty = prev;
prev = !prev;
}
Стас
Немного переделал пример, чтобы было понятнее (+ в первоначальном была ошибка):
+ Показать
− Скрыть
#include <chrono>
#include <vector>
#include <iostream>
#include <DirectXMath.h>
using namespace DirectX;
#include <xmmintrin.h>
struct __declspec(align(16)) Test_OO_no_dirty
{
XMMATRIX matrix;
__forceinline void Update()
{
matrix = XMMatrixIdentity();
matrix = XMMatrixMultiply(matrix, matrix);
#if 0
matrix = XMMatrixMultiply(matrix, matrix);
matrix = XMMatrixMultiply(matrix, matrix);
#endif
}
};
struct __declspec(align(16)) Test_OO_dirty
{
bool dirty;
XMMATRIX matrix;
Test_OO_dirty()
{
dirty = true;
}
__forceinline void Update()
{
if (dirty) {
matrix = XMMatrixIdentity();
matrix = XMMatrixMultiply(matrix, matrix);
#if 0
matrix = XMMatrixMultiply(matrix, matrix);
matrix = XMMatrixMultiply(matrix, matrix);
#endif
}
dirty = !dirty;
}
};
struct __declspec(align(16)) Test_DO_dirty
{
bool dirty;
XMMATRIX matrix;
__declspec(noinline) static void Update(std::vector<Test_DO_dirty>& data)
{
int numItems = 0;
const size_t kItemsPerBucket = 4;
_mm_prefetch(reinterpret_cast<const char*>(data.data()), _MM_HINT_T0);
for (size_t i = 0; i < data.size() / kItemsPerBucket; i += kItemsPerBucket) {
_mm_prefetch(reinterpret_cast<const char*>(data.data()) + kItemsPerBucket * sizeof(Test_DO_dirty), _MM_HINT_T0);
for (size_t j = 0; j < kItemsPerBucket; ++j) {
Test_DO_dirty* item = &data[i + j];
if (!item->dirty) {
item->dirty = !item->dirty;
continue;
} else {
numItems++;
item->matrix = XMMatrixIdentity();
item->matrix = XMMatrixMultiply(item->matrix, item->matrix);
#if 0
item->matrix = XMMatrixMultiply(item->matrix, item->matrix);
item->matrix = XMMatrixMultiply(item->matrix, item->matrix);
#endif
item->dirty = !item->dirty;
}
}
}
std::cout << "dirty num items: " << numItems << std::endl;;
}
};
struct __declspec(align(16)) Test_DO_no_dirty
{
XMMATRIX matrix;
__declspec(noinline) static void Update(std::vector<Test_DO_no_dirty>& data)
{
int numItems = 0;
const size_t kItemsPerBucket = 4;
_mm_prefetch(reinterpret_cast<const char*>(data.data()), _MM_HINT_T0);
for (size_t i = 0; i < data.size() / kItemsPerBucket; i += kItemsPerBucket) {
_mm_prefetch(reinterpret_cast<const char*>(data.data()) + kItemsPerBucket * sizeof(Test_DO_no_dirty), _MM_HINT_T0);
for (size_t j = 0; j < kItemsPerBucket; ++j) {
Test_DO_no_dirty* item = &data[i + j];
item->matrix = XMMatrixIdentity();
item->matrix = XMMatrixMultiply(item->matrix, item->matrix);
#if 0
item->matrix = XMMatrixMultiply(item->matrix, item->matrix);
item->matrix = XMMatrixMultiply(item->matrix, item->matrix);
#endif
numItems++;
}
}
std::cout << "no_dirty num items: " << numItems << std::endl;
}
};
int main()
{
const int kNumIterations = 8;
{
std::vector<Test_OO_no_dirty> testData;
testData.resize(4 * 1024 * 1024);
auto tmStart = std::chrono::high_resolution_clock::now();
for (auto obj: testData)
for (int i = 0; i < kNumIterations; ++i)
obj.Update();
auto tmEnd = std::chrono::high_resolution_clock::now();
std::cout << "Test_OO_no_dirty: " << std::chrono::duration_cast<std::chrono::duration<double>>(tmEnd - tmStart).count() << std::endl;
}
_mm_sfence();
{
std::vector<Test_OO_dirty> testData;
testData.resize(4 * 1024 * 1024);
auto tmStart = std::chrono::high_resolution_clock::now();
for (auto obj: testData)
for (int i = 0; i < kNumIterations; ++i)
obj.Update();
auto tmEnd = std::chrono::high_resolution_clock::now();
std::cout << "Test_OO_dirty: " << std::chrono::duration_cast<std::chrono::duration<double>>(tmEnd - tmStart).count() << std::endl;
}
_mm_sfence();
{
std::vector<Test_DO_dirty> testData;
testData.resize(4 * 1024 * 1024);
for (size_t i = 0; i < testData.size(); ++i)
testData[i].dirty = (i % 2) != 0;
auto tmStart = std::chrono::high_resolution_clock::now();
for (int i = 0; i < kNumIterations; ++i)
Test_DO_dirty::Update(testData);
auto tmEnd = std::chrono::high_resolution_clock::now();
std::cout << "Test_DO_dirty: " << std::chrono::duration_cast<std::chrono::duration<double>>(tmEnd - tmStart).count() << std::endl;
}
_mm_sfence();
{
std::vector<Test_DO_no_dirty> testData;
testData.resize(4 * 1024 * 1024);
auto tmStart = std::chrono::high_resolution_clock::now();
for (int i = 0; i < kNumIterations; ++i)
Test_DO_no_dirty::Update(testData);
auto tmEnd = std::chrono::high_resolution_clock::now();
std::cout << "Test_DO_no_dirty: " << std::chrono::duration_cast<std::chrono::duration<double>>(tmEnd - tmStart).count() << std::endl;
}
int i;
std::cin >> i;
return 0;
}
Результаты:

Я оставил только одно умножение матрицы, у меня получилось что первый вариант с бранчем считает 500к матриц за примерно такое же время, которое второму варианту достаточно чтобы посчитать 100к матриц.
Разница "всего-лишь" в 2 раза, поэтому в данном конкретном случае я считаю что флаг надо выкинуть.
}:+()___ [Smile]
> А причем здесь кешмиссы? Основные тормоза от таких флагов возникают из-за
> branch-misprediction & pipeline stall.
В данном случае разница в скорости между data driven и object-driven подходами обьясняется именно кешмиссами.
Разница между двумя data-driven подходами (с флагом и без) равна: кеш+миспредикт+столл. Но я до этого ещё не успел дойти :)
Кстати, branch misprediction на x86 не такой страшный, как, например, на Cell. Хотя могу ошибатся.
> Кстати, советую проверить, что будет, если в конструкторах вместо
> {
> dirty = true;
> }
>сделать
> {
> static bool prev = true;
> dirty = prev;
> prev = !prev;
> }
Ничего особенного не будет.
bazhenovc
> Разница "всего-лишь" в 2 раза, поэтому в данном конкретном случае я считаю что
> флаг надо выкинуть.
У тебя сейчас фактически можно проверять dirty flag и если он false то прервать весь цикл. В этом проблема этого теста.
Кстати только счас сообразил, у тебя твои тесты выровнены на 16 байт но при этом вариант с флагом у тебя больше чем вариант без флага,
Соответственно твой тест не такой уж и точный.
bazhenovc
> Ничего особенного не будет.
Он тебе предложил чтобы объекты чередовались, а не каждый объект апдейтился.
Стас
> У тебя сейчас фактически можно проверять dirty flag и если он false то прервать
> весь цикл. В этом проблема этого теста.
>
> Кстати только счас сообразил, у тебя твои тесты выровнены на 16 байт но при
> этом вариант с флагом у тебя больше чем вариант без флага,
> Соответственно твой тест не такой уж и точный.
Ладно, ок, не буду дальше спорить.
bazhenovc
> Ладно, ок, не буду дальше спорить.
+ Показать
− Скрыть
#include <chrono>
#include <vector>
#include <iostream>
#include <DirectXMath.h>
using namespace DirectX;
#include <xmmintrin.h>
struct __declspec(align(16)) Test_OO_no_dirty
{
bool dirty;
XMMATRIX matrix;
__forceinline void Update()
{
matrix = XMMatrixIdentity();
matrix = XMMatrixMultiply(matrix, matrix);
#if 0
matrix = XMMatrixMultiply(matrix, matrix);
matrix = XMMatrixMultiply(matrix, matrix);
#endif
}
};
struct __declspec(align(16)) Test_OO_dirty
{
bool dirty;
XMMATRIX matrix;
Test_OO_dirty()
{
dirty = (rand()%1)!=0;
}
__forceinline void Update()
{
if (dirty) {
matrix = XMMatrixIdentity();
matrix = XMMatrixMultiply(matrix, matrix);
#if 0
matrix = XMMatrixMultiply(matrix, matrix);
matrix = XMMatrixMultiply(matrix, matrix);
#endif
}
dirty = !dirty;
}
};
struct __declspec(align(16)) Test_DO_dirty
{
bool dirty;
XMMATRIX matrix;
Test_DO_dirty()
{
dirty = (rand() % 1) != 0;
}
__declspec(noinline) static void Update(std::vector<Test_DO_dirty>& data)
{
int numItems = 0;
const size_t kItemsPerBucket = 4;
_mm_prefetch(reinterpret_cast<const char*>(data.data()), _MM_HINT_T0);
for (size_t i = 0; i < data.size() / kItemsPerBucket; i += kItemsPerBucket) {
_mm_prefetch(reinterpret_cast<const char*>(data.data()) + kItemsPerBucket * sizeof(Test_DO_dirty), _MM_HINT_T0);
for (size_t j = 0; j < kItemsPerBucket; ++j) {
Test_DO_dirty* item = &data[i + j];
if (!item->dirty) {
item->dirty = !item->dirty;
continue;
}
else {
numItems++;
item->matrix = XMMatrixIdentity();
item->matrix = XMMatrixMultiply(item->matrix, item->matrix);
#if 0
item->matrix = XMMatrixMultiply(item->matrix, item->matrix);
item->matrix = XMMatrixMultiply(item->matrix, item->matrix);
#endif
item->dirty = !item->dirty;
}
}
}
std::cout << "dirty num items: " << numItems << std::endl;;
}
};
struct __declspec(align(16)) Test_DO_no_dirty
{
bool dirty;
XMMATRIX matrix;
__declspec(noinline) static void Update(std::vector<Test_DO_no_dirty>& data)
{
int numItems = 0;
const size_t kItemsPerBucket = 4;
_mm_prefetch(reinterpret_cast<const char*>(data.data()), _MM_HINT_T0);
for (size_t i = 0; i < data.size() / kItemsPerBucket; i += kItemsPerBucket) {
_mm_prefetch(reinterpret_cast<const char*>(data.data()) + kItemsPerBucket * sizeof(Test_DO_no_dirty), _MM_HINT_T0);
for (size_t j = 0; j < kItemsPerBucket; ++j) {
Test_DO_no_dirty* item = &data[i + j];
item->matrix = XMMatrixIdentity();
item->matrix = XMMatrixMultiply(item->matrix, item->matrix);
#if 0
item->matrix = XMMatrixMultiply(item->matrix, item->matrix);
item->matrix = XMMatrixMultiply(item->matrix, item->matrix);
#endif
numItems++;
}
}
std::cout << "no_dirty num items: " << numItems << std::endl;
}
};
int main()
{
const int itemCount = 20 * 1024 * 1024;
{
std::vector<Test_OO_no_dirty> testData;
testData.resize(itemCount);
auto tmStart = std::chrono::high_resolution_clock::now();
for (auto & obj : testData)
{
obj.Update();
}
auto tmEnd = std::chrono::high_resolution_clock::now();
std::cout << "Test_OO_no_dirty: " << std::chrono::duration_cast<std::chrono::duration<double>>(tmEnd - tmStart).count() << std::endl;
}
_mm_sfence();
{
std::vector<Test_OO_dirty> testData;
testData.resize(itemCount);
auto tmStart = std::chrono::high_resolution_clock::now();
for (auto & obj : testData)
{
obj.Update();
}
auto tmEnd = std::chrono::high_resolution_clock::now();
std::cout << "Test_OO_dirty: " << std::chrono::duration_cast<std::chrono::duration<double>>(tmEnd - tmStart).count() << std::endl;
}
_mm_sfence();
{
std::vector<Test_DO_dirty> testData;
testData.resize(itemCount);
auto tmStart = std::chrono::high_resolution_clock::now();
Test_DO_dirty::Update(testData);
auto tmEnd = std::chrono::high_resolution_clock::now();
std::cout << "Test_DO_dirty: " << std::chrono::duration_cast<std::chrono::duration<double>>(tmEnd - tmStart).count() << std::endl;
}
_mm_sfence();
{
std::vector<Test_DO_no_dirty> testData;
testData.resize(itemCount);
auto tmStart = std::chrono::high_resolution_clock::now();
Test_DO_no_dirty::Update(testData);
auto tmEnd = std::chrono::high_resolution_clock::now();
std::cout << "Test_DO_no_dirty: " << std::chrono::duration_cast<std::chrono::duration<double>>(tmEnd - tmStart).count() << std::endl;
}
int i;
std::cin >> i;
return 0;
}
Я немного модифицировал твой тест. Во первых итерации не дают ни чего, после первой итерации у тебя все объекты по считаны и уже не меняются.
Поэтому я просто по рнд выставил флаг
И в первом случае у тебя была ошибка:
for( auto obj : test ); // Тут ты создаешь временную копию каждого объекта
должно быть:
for( auto & obj : test );
Вот результат:
Test_OO_no_dirty: 0.59906
Test_OO_dirty: 0.167017
Test_DO_dirty: 0.0420042
Test_DO_no_dirty: 0.170017
Как видишь флаг dirty дает ОЧЕНЬ ощутимый прирост
Стас
> dirty = (rand() % 1) != 0;
Это не корректно, у тебя слишком разное кол-во объектов просчитывается. Замеряй на одинаковом.
Хотя в любом случае я был прав с data-driven подходом, он всё равно быстрее :)
Кстати, почему кол-во итемов в случае с dirty == 0?
Естественно если ты ни одну матрицу не посчитал, оно будет быстрее.
Кстати, там ещё одна ошибка: неправильный префетч. Надо так:
_mm_prefetch(reinterpret_cast<const char*>(data.data()) + i + kItemsPerBucket * sizeof(Test_DO_dirty), _MM_HINT_T0);
bazhenovc
> Это не корректно, у тебя слишком разное кол-во объектов просчитывается. Замеряй
> на одинаковом.
Это как раз корректно, потому что у тебя только в тепличных условиях одинаковое количество нужно посчитать, а в реальных условиях у тебя одновременно апдейтится будет скорее всего не так уж много объектов.
bazhenovc
> Хотя в любом случае я был прав с data-driven подходом, он всё равно быстрее :)
А я как бы с этим и не спорю.
Мое возражение по поводу твоих утверждений в том что, dirty флаг, так же как и его отсутвие, нельзя вырывать из контекста, и нужно смотреть применительно к задаче.