Здравствуйте все. Я использую AMP C ++, чтобы сделать некоторые вычисления объема.
У меня миллионы тетраэдров с одной точкой (0,0,0). так что я могу получить объем тетраэдров простым способом:
sum += triangle.x1 * triangle.y2 * triangle.z3 + \
triangle.y1 * triangle.z2 * triangle.x3 + \
triangle.x2 * triangle.y3 * triangle.z1 - \
triangle.x3 * triangle.y2 * triangle.z1 - \
triangle.x2 * triangle.y1 * triangle.z3 - \
triangle.y3 * triangle.z2 * triangle.x1;
Итак, я хочу ускорить мои вычисления с помощью AMP C ++.
Вот код
typedef struct
{
double x1;
double y1;
double z1;
double x2;
double y2;
double z2;
double x3;
double y3;
double z3;
} Triangle;
И основная функция:
accelerator my_accelerator(accelerator::default_accelerator);
accelerator_view acc_view = my_accelerator.get_default_view();
const int BLOCK_SIZE = 64;
int outputSize = int(numTriangles / BLOCK_SIZE);
int dimA = int(numTriangles / BLOCK_SIZE) * BLOCK_SIZE;
std::cout<<dimA<<std::endl;
//copy triangles from host to device
array<Triangle,1> triangle(numTriangles);
copy(vTriangle.begin(),vTriangle.end(), triangle);
//Volume
std::vector<double> volumeCPP;
for (int i=0; i < outputSize; i++)
{
volumeCPP.push_back(double(0));
}
array_view<double,1> volume(outputSize,volumeCPP);
volume.discard_data();
clock_t start,finish;
start = clock();
parallel_for_each(
volume.extent.tile<1>(),
[=, &triangle](tiled_index<1> t_idx) restrict(amp)
{
double sum = 0.0f;
tile_static Triangle tile_triangle[4];
tile_triangle[t_idx.local[0]] = triangle[t_idx.global];
if (t_idx.local[0] == 0)
{
for (int idx=0; idx < BLOCK_SIZE; idx++){
sum += tile_triangle[idx].x1 * tile_triangle[idx].y2 * tile_triangle[idx].z3 + tile_triangle[idx].y1 * tile_triangle[idx].z2 * tile_triangle[idx].x3 + tile_triangle[idx].x2 * tile_triangle[idx].y3 * tile_triangle[idx].z1 - tile_triangle[idx].x3 * tile_triangle[idx].y2 * tile_triangle[idx].z1 - tile_triangle[idx].x2 * tile_triangle[idx].y1 * tile_triangle[idx].z3 - tile_triangle[idx].y3 * tile_triangle[idx].z2 * tile_triangle[idx].x1;
//t_idx.barrier.wait();
}
//t_idx.barrier.wait();
}
volume[t_idx.global] = sum;
}
);
acc_view.wait();
finish = clock();
copy(volume, volumeCPP.begin());
Таким образом, каждая работа имеет вниз. Но интересные вещи есть. Это стоит больше, чем процессор (одноядерный) код.
C ++ на CPU (одноядерный) стоит 0,085 секунды, чтобы завершить вычисление 1024 * 1024 * 2 треугольников.
Но код AMP C ++ стоит 0,530 секунды. гораздо больше, чем код C ++.
После поиска в интернете есть подсказка: если мы сначала прогреем устройство, мы сможем рассчитать «реальные» затраты времени при расчете.
Поэтому я сначала вычисляю 128 треугольников, чтобы прогреть устройство (стоит около 0,2 секунды), а затем вычисляю объем, рассчитав 1024 * 1024 * 2 треугольника. Он стал намного быстрее (стоит около 0,091 секунды), но все же медленнее, чем код процессора (одноядерный).
Я хотел бы знать, почему, и любой, кто может помочь мне ускорить расчет.
Большое спасибо.
Во-первых, ниже, я думаю, что это немного лучшая реализация с некоторыми комментариями. Ваш код делает некоторые вещи, которых можно избежать.
Однако то, что вы действительно делаете здесь, это сокращение. Это алгоритм, который был очень тщательно исследован и оптимизирован. Есть реализация C ++ AMP на Сайт AMP Algorithms Codeplex Он реализован в виде алгоритма в стиле STL. Прежде чем прийти к выводу, что C ++ AMP не соответствует вашим потребностям, я бы попробовал использовать эту реализацию, так как это будет тривиально и может дать вам гораздо лучшую производительность. Мне было бы интересно посмотреть, как вы поживаете.
Сайт AMP Book Codeplex содержит вспомогательный класс для синхронизации ядер C ++ AMP. В сопровождающей книге также обсуждается внедрение сокращения. Там есть целая глава.
void Foo()
{
const int numTriangles = 128;
std::vector<Triangle> vTriangle;
accelerator my_accelerator(accelerator::default_accelerator);
accelerator_view acc_view = my_accelerator.get_default_view();
const int BLOCK_SIZE = 64;
int outputSize = int(numTriangles / BLOCK_SIZE);
const int dimA = numTriangles;
std::cout<<dimA<<std::endl;
//copy triangles from host to device
// Use and array_view to automatically sync your data.
// You can use acc_view.flush() to make sure that copy is complete
// when you are running your timing code. Make this const so that AMP does
// not copy your input data back to the CPU.
array_view<const Triangle, 1> triangle(vTriangle.size(), vTriangle.data());
//Volume
// Don't push_back this causes (re)allocation as the vector grows.
// Set size and fill at the same time.
std::vector<double> volumeCPP(outputSize, 0.0);
array_view<double, 1> volume(outputSize, volumeCPP);
volume.discard_data();
// I would use the timing code on CodePlex.
// It will be more accurate than this.
clock_t start, finish;
start = clock();
parallel_for_each(
// Not sure a tile size of 1 will be handled that
// well by the runtime in terms of perf. I see why you
// are doing it to get tile_static. You might be better off having larger tiles.
volume.extent.tile<1>(),
[=](tiled_index<1> t_idx) restrict(amp)
{
double sum = 0.0f;
for (int idx = 0; idx < BLOCK_SIZE; idx++)
{
// Loading the single triangle into tiled memory is a good idea because
// elements are read more than once.
tile_static Triangle tile_triangle;
tile_triangle = triangle[t_idx.global * BLOCK_SIZE + idx];
sum += tile_triangle.x1 * tile_triangle.y2 * tile_triangle.z3 +
tile_triangle.y1 * tile_triangle.z2 * tile_triangle.x3 +
tile_triangle.x2 * tile_triangle.y3 * tile_triangle.z1 -
tile_triangle.x3 * tile_triangle.y2 * tile_triangle.z1 -
tile_triangle.x2 * tile_triangle.y1 * tile_triangle.z3 -
tile_triangle.y3 * tile_triangle.z2 * tile_triangle.x1;
}
volume[t_idx.global] = sum;
}
);
// Force data copy back to CPU.
volume.synchronize();
double sum = std::accumulate(begin(volumeCPP), end(volumeCPP), 0.0);
}
Вот еще один пример, в котором используется библиотека алгоритмов AMP для реализации решения вашей проблемы с использованием шаблона map / lower.
std::vector<Triangle> triangles_cpu(1000);
array_view<const Triangle, 1> triangles_gpu(triangles_cpu.size(), triangles_cpu.data());
concurrency::array<double, 1> volumes_gpu(triangles_cpu.size());
array_view<double, 1> volumes_gpuvw(volumes_gpu);
amp_stl_algorithms::transform(begin(triangles_gpu), end(triangles_gpu), begin(volumes_gpuvw),
[=](const triangle& t) restrict(amp)
{
return t.x1 * (t.y2 * t.z3 - t.y3 * t.z2)
+ t.y1 * (t.z2 * t.x3 - t.x2 * t.z3)
+ t.z1 * (t.x2 * t.y3 - t.x3 * t.y2);
});
double sum = amp_stl_algorithms::reduce(begin(volumes_gpuvw), end(volumes_gpuvw), 0.0);
Вы должны быть в состоянии немного ускорить его, вычтя.
Обратите внимание, что ваша формула для объема тетраэдра:
+ x1 * y2 * z3
+ y1 * z2 * x3
+ x2 * y3 * z1
- x3 * y2 * z1
- x2 * y1 * z3
- y3 * z2 * x1
эквивалентно:
+ x1 * (y2 * z3 - y3 * z2)
+ y1 * (z2 * x3 - x2 * z3)
+ z1 * (x2 * y3 - x3 * y2)
Исходная формула имеет 12 умножений, а эквивалентная формула имеет 9 умножений (на 25% меньше). Трудно сказать, насколько велико будет общее улучшение, но я не удивлюсь, если оно даст вам 20%.