Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Ладно, вместо тысячи слов — встречайте её могущество копипаста без boost'а. Соотношения получились примерно те же самые
1) По твоему тесту:
Во-первых, как я понял, ты пытаешься измерить задержки из-за cache-miss с помощью listа. Думаю, что это не очень хороший подоход, т.к. элементы list могут быть расположены в памяти последовательно. В моем же тесте, я точно обращаюсь к элементам в случайном порядке.
Во-вторых, ты измеряешь производительность stl контейнеров, а не процессора.
2) По моему тесту:
В моем измерении тоже были ошибки: я компилировал без оптимизатора, а когда подключил оптимизатор он выкинул весь расчет. В конце поста я приведу для конкретности последний вариант своего кода.
Результаты изменились:
Последовательное сложение с индирекцией оказалось ровно в два раза медленне, чем с индирекцией. Это ожидаемо, т.к. выполняется две инструкции, вместо одной.
Сложение со случайным доступом(и вечным cache-miss) оказалось еще в два раза медленнее, чем с последовательным. Это обозначает верхнюю границу эффективности кэша при доступе к данным, и для меня интересно.
Предлагаю на этом результате остановиться, т.к. он меряет именно скорость процессора, а не скорость контейнеров stl. Кстати, только vector<int> оказался по скорости близок к массивам, а в остальных вариантах при одном, по сути, алгоритме твоя программа с stl, действительно, работает на порядки медленнее моей при одном размере массива(что удивило даже меня).
Относительно причин задержек Java/C# vs C++ думаю, что лишний код присущ JIT-компиляторам, и лишние индирекции часть этой проблемы. Однако, программируя на C++, очень редко есть смысл заботиться об индирекции, т.к. далеко не все функции в программе занимают значительное время. И алгоритм работы(особенно не относящийся к вычислительным) почти никогда не состоит из одной инструкции сложения чисел, поэтому обращения к памяти вносят меньший вклад во время выполнения, чем в этих синтетических тестах. Поэтому для меня лично кэш-миссы и индирекции недостаточная причина всегда использовать move-семантику для каждого вектора больших объектов(т.к. это слишком мелкая микрооптимизация, усложняющая код) и все же буду использовать указатели, а при тяжелых вычислениях я постараюсь обойтись без stl. Вообще, C++ быстр сам по себе и программа будет работать быстрее, чем C#/Java, в любом варианте.
Привожу мой тест в последнем варианте:
#include <iostream>
#include <chrono>
#include <stdlib.h>
using namespace std;
#define SIZE 4000*1000
int *array;
int *map1, *map2;
int test(int *map)
{
int sum = 0;
for (int i=0; i<SIZE; i++)
sum += array[map[i]];
return sum;
}
int test0()
{
int sum = 0;
for (int i=0; i<SIZE; i++)
sum += array[i];
return sum;
}
int main()
{
srand(time(0));
array = new int[SIZE];
map1 = new int[SIZE];
for (int i=0;i<SIZE;i++) {
map1[i] = i;
array[i] = random();
}
map2 = new int[SIZE];
for (int i=0;i<SIZE;i++)
map2[i] = random()%SIZE;
auto start0 = std::chrono::high_resolution_clock::now();
int r = test0();
auto finish0 = std::chrono::high_resolution_clock::now();
cout << r << ":" <<std::chrono::duration_cast<std::chrono::nanoseconds>(finish0-start0).count() << "ns\n";
auto start1 = std::chrono::high_resolution_clock::now();
r = test(map1);
auto finish1 = std::chrono::high_resolution_clock::now();
cout << r << ":" << std::chrono::duration_cast<std::chrono::nanoseconds>(finish1-start1).count() << "ns\n";
auto start2 = std::chrono::high_resolution_clock::now();
r = test(map2);
auto finish2 = std::chrono::high_resolution_clock::now();
cout << r << ":" << std::chrono::duration_cast<std::chrono::nanoseconds>(finish2-start2).count() << "ns\n";
}
У сложных вещей обычно есть и хорошие, и плохие аспекты.
Берегите Родину, мать вашу. (ДДТ)