Сборник кусков кода
Различные примеры кода.
О производительности структур данных
19.10.2012
|
Abyx |
#include <cstdlib>
#include <algorithm>
#include <chrono>
#include <iostream>
#include <iomanip>
#include <numeric>
#include <string>
#include <thread>
#include <vector>
struct Obj
{
Obj* next;
unsigned x;
Obj() : x(::rand()) {}
void update()
{
++x; // как-то меняем данные объекта
}
};
template<typename F>
void test(const char* name, F f)
{
std::vector<unsigned> times(100);
for (auto& dt : times)
{
std::this_thread::yield();
auto t1 = std::chrono::high_resolution_clock::now();
f();
auto t2 = std::chrono::high_resolution_clock::now();
dt = (unsigned)std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count();
}
std::cout << name << ": " << std::accumulate(times.begin(), times.end(), 0) / times.size() << " ms" << std::endl;
}
int main()
{
std::srand((unsigned)std::time(nullptr));
std::vector<Obj> vObj(1 * 1000 * 1000);
std::vector<Obj*> vPtr;
for (auto& o : vObj)
vPtr.push_back(&o);
std::random_shuffle(vPtr.begin(), vPtr.end()); // убиваем memory locality при последовательном доступе
Obj* lst = nullptr;
for (auto p : vPtr)
{
p->next = lst;
lst = p;
}
test("vector<Obj>", [&]{ for (auto& o : vObj) o.update(); });
test("vector<Obj*>", [&]{ for (auto& o : vPtr) o->update(); });
test("intrusive list", [&]{ for (auto o = lst; o; o = o->next) o->update(); });
}
Типичный результат на моей машине
vector<Obj>: 0 ms
vector<Obj*>: 10 ms
intrusive list: 76 ms
Почему так?
В случае с vector<Obj> объект лежат друг за другом, и эффективно читаются в кеш процессора
У vector<Obj*> перемешаны указатели, по этому кешировать доступ к данным объектов не получается.
Однако, сам vector<Obj*> прекрасно кешируется, а вот для связанного списка кеширование не работает вообще.
19.10.2012 23 комментария |
W>Говорить о "производительности структур данных" — некорректно. Производительность может быть у алгоритмов, работающих на этих структурах. А еще точнее — у конкретных реализаций этих алгоритмов.
точно?
в некоторых случаях, скорость работы с вот такими массивами
int A[MaxX][MaxY]
и
int B[MaxY][MaxX]
при использовании одних и тех же алгоритмов, может различаться на несколько порядков, т.е. настолько сильно, что иногда приходится это учитывать и оптимизировать.
может это быть свяхано с тем что у последней структуры степень параллелизма меньще??
понятно что если это вектор указаетлей то операции update над разными элементами независимы и даже на 1 роцуссоре конвейер моэет быть испольхован более эффективно
А у последней структуры каждый следующий элемммент хависит от предыдущего
A>>
D>может это быть свяхано с тем что у последней структуры степень параллелизма меньще??
степень чего?
(похоже, я не в курсе)
http://www.rsdn.ru/forum/philosophy/2929998.1
ну вообще-то в последних двух случаях одинаково рандомный доступ к памяти. Вопрос в том почему 2 последних отличаются. То что первый бысттрее это всем понятно.
Ф>>http://www.rsdn.ru/forum/philosophy/2929998.1
D>ну вообще-то в последних двух случаях одинаково рандомный доступ к памяти. Вопрос в том почему 2 последних отличаются.
точно?
Ф>точно?
указатели лежат рядом с данными
Я уверен, что количество cache misses несильно отличается в 2 последних случаях
Разница именно в степени независимости данных, что дает разную загрузку конвейера
A>Почему так?
A>В случае с vector<Obj> объект лежат друг за другом, и эффективно читаются в кеш процессора
A>У vector<Obj*> перемешаны указатели, по этому кешировать доступ к данным объектов не получается.
A>Однако, сам vector<Obj*> прекрасно кешируется, а вот для связанного списка кеширование не работает вообще.
Разницу дает вот что
Слишком много лишних присваиваний. То есть, нужно взять да глянуть в ассемблерный код, что бы убедиться, что третий вариант цикла дает самый неэффективный код, а массивы очень сильно оптимизированы.
A>>Почему так?
A>>В случае с vector<Obj> объект лежат друг за другом, и эффективно читаются в кеш процессора
A>>У vector<Obj*> перемешаны указатели, по этому кешировать доступ к данным объектов не получается.
A>>Однако, сам vector<Obj*> прекрасно кешируется, а вот для связанного списка кеширование не работает вообще.
I>Разницу дает вот что
I>
I>Слишком много лишних присваиваний. То есть, нужно взять да глянуть в ассемблерный код, что бы убедиться, что третий вариант цикла дает самый неэффективный код, а массивы очень сильно оптимизированы.
*много* это одно?
ассемблерный код несложно сгенерить в уме:
for (auto& o : vPtr) ++o->x; это
for (auto o = lst; o; o = o->next) ++o->x; это
A>*много* это одно?
A>ассемблерный код несложно сгенерить в уме:
Сложно, и ты, к тому же, привел не тот код.
A>>ассемблерный код несложно сгенерить в уме:
I>Сложно, и ты, к тому же, привел не тот код.
почему не тот? где там разница?
I>00201446 mov edx,dword ptr [edx] // причина тормозов
и в чем тут причина? лишнее присваивание?
I>>Сложно, и ты, к тому же, привел не тот код.
A>почему не тот? где там разница?
А что, лень поставить два фрагмента в один редактор и сравнить ?
I>>00201446 mov edx,dword ptr [edx] // причина тормозов
A>и в чем тут причина? лишнее присваивание?
AGI
I>Здравствуйте, Abyx, Вы писали:
I>>>Сложно, и ты, к тому же, привел не тот код.
A>>почему не тот? где там разница?
I>А что, лень поставить два фрагмента в один редактор и сравнить ?
я сравнил и не заметил разницы, за исключением разных регистров и т.п.
I>>>00201446 mov edx,dword ptr [edx] // причина тормозов
A>>и в чем тут причина? лишнее присваивание?
I>AGI
что? http://www.urbandictionary.com/define.php?term=AGI
I>>А что, лень поставить два фрагмента в один редактор и сравнить ?
A>я сравнил и не заметил разницы, за исключением разных регистров и т.п.
Плохо сравнивал.
I>>>>00201446 mov edx,dword ptr [edx] // причина тормозов
A>>>и в чем тут причина? лишнее присваивание?
I>>AGI
A>что? http://www.urbandictionary.com/define.php?term=AGI
И давно ты специальные термины на urbandictionary смотришь ?
Address Generation Interlock