我希望实现一个简单的Mandelbrot集合绘制程序,使用不同种类的HPC范式来展示它们的优势和劣势,以及它们的实现难易程度。考虑到GPGPU(CUDA/OpenACC/OpenMP4.5)、线程/OpenMP和MPI。并使用这些示例为新手程序员提供指导,并展示可能性。代码的清晰度比从硬件中获得绝对最高性能更重要,那是第二步;)
因为该问题容易并行化,而且现代CPU可以通过矢量指令获得大量性能,所以我还想结合OpenMP和SIMD。不幸的是,简单地添加#pragma omp simd并不能产生令人满意的结果,使用内嵌函数也不太友好或具有未来可扩展性,如此处所示。
幸运的是,正在进行C++标准的工作,使得通用地实现矢量指令应该更容易,如TS中所述:“并行扩展,版本2”,特别是第9节数据并行类型。可以在此处找到WIP实现,它基于可以在此处找到的VC。
假设我有以下类(已更改为使其更简单)。
我们可以假设 x 和 y 方向的分辨率都是 2/4/8/... 的倍数,具体取决于我们使用哪些 SIMD 指令。
不幸的是,在 std::experimental::simd 上线上几乎找不到任何信息。就我所知,也没有任何非平凡的示例。
在 Vc git 存储库中,有一个 Mandelbrot 集计算器的实现,但它相当复杂,并且由于缺少注释而难以跟踪。
很明显,我应该改变函数 computeMandelbrot() 中 double 的数据类型,但我不确定应该改成什么类型。 TS 提到了两种主要的新数据类型,适用于某种类型 T,
native_simd = std::experimental::simd;
和
fixed_size_simd = std::experimental::simd>;
使用 native_simd 最有意义,因为我不知道编译时的界限。但是我不清楚这些类型代表什么,native_simd 是单个 double 还是执行矢量指令的多个 double 集合?这个集合中有多少个 double?
如果有人能向我指出使用这些概念的示例,或者给我一些关于如何使用 std::experimental::simd 实现矢量指令的指导,我将非常感激。
因为该问题容易并行化,而且现代CPU可以通过矢量指令获得大量性能,所以我还想结合OpenMP和SIMD。不幸的是,简单地添加#pragma omp simd并不能产生令人满意的结果,使用内嵌函数也不太友好或具有未来可扩展性,如此处所示。
幸运的是,正在进行C++标准的工作,使得通用地实现矢量指令应该更容易,如TS中所述:“并行扩展,版本2”,特别是第9节数据并行类型。可以在此处找到WIP实现,它基于可以在此处找到的VC。
假设我有以下类(已更改为使其更简单)。
#include <stddef.h>
using Range = std::pair<double, double>;
using Resolution = std::pair<std::size_t, std::size_t>;
class Mandelbrot
{
double* d_iters;
Range d_xrange;
Range d_yrange;
Resolution d_res;
std::size_t d_maxIter;
public:
Mandelbrot(Range xrange, Range yrange, Resolution res, std::size_t maxIter);
~Mandelbrot();
void writeImage(std::string const& fileName);
void computeMandelbrot();
private:
void calculateColors();
};
以下是使用OpenMP实现的computeMandelbrot()
:
void Mandelbrot::computeMandelbrot()
{
double dx = (d_xrange.second - d_xrange.first) / d_res.first;
double dy = (d_yrange.second - d_yrange.first) / d_res.second;
#pragma omp parallel for schedule(dynamic)
for (std::size_t row = 0; row != d_res.second; ++row)
{
double c_imag = d_yrange.first + row * dy;
for (std::size_t col = 0; col != d_res.first; ++col)
{
double real = 0.0;
double imag = 0.0;
double realSquared = 0.0;
double imagSquared = 0.0;
double c_real = d_xrange.first + col * dx;
std::size_t iter = 0;
while (iter < d_maxIter && realSquared + imagSquared < 4.0)
{
realSquared = real * real;
imagSquared = imag * imag;
imag = 2 * real * imag + c_imag;
real = realSquared - imagSquared + c_real;
++iter;
}
d_iters[row * d_res.first + col] = iter;
}
}
}
我们可以假设 x 和 y 方向的分辨率都是 2/4/8/... 的倍数,具体取决于我们使用哪些 SIMD 指令。
不幸的是,在 std::experimental::simd 上线上几乎找不到任何信息。就我所知,也没有任何非平凡的示例。
在 Vc git 存储库中,有一个 Mandelbrot 集计算器的实现,但它相当复杂,并且由于缺少注释而难以跟踪。
很明显,我应该改变函数 computeMandelbrot() 中 double 的数据类型,但我不确定应该改成什么类型。 TS 提到了两种主要的新数据类型,适用于某种类型 T,
native_simd = std::experimental::simd;
和
fixed_size_simd = std::experimental::simd>;
使用 native_simd 最有意义,因为我不知道编译时的界限。但是我不清楚这些类型代表什么,native_simd 是单个 double 还是执行矢量指令的多个 double 集合?这个集合中有多少个 double?
如果有人能向我指出使用这些概念的示例,或者给我一些关于如何使用 std::experimental::simd 实现矢量指令的指导,我将非常感激。
bool any_of(const simd_mask<T, Abi>&)
和类似函数来测试vec1 < vec2
simd_mask结果,例如x86的movmskpd
(_mm_movemask_pd
)让您根据每个元素比较结果的所有/任意进行分支。因此,您可以使用它来实现Mandelbrot,但我建议先选择一个更适合SIMD的问题。 - Peter Cordesstd::simd
的人,因为目前我还没有找到任何例子。这很有道理,因为它仍然是实验性的。但这也是发布这个问题的原因之一,以便其他想要使用这个构造的人可以找到这个问题。 - Nigel Overmars