- c - 在位数组中找到第一个零
- linux - Unix 显示有关匹配两种模式之一的文件的信息
- 正则表达式替换多个文件
- linux - 隐藏来自 xtrace 的命令
我正在寻找有关如何使用 SSE 进行并行前缀和的一些建议。我有兴趣在整数、 float 或 double 组上执行此操作。
我想出了两个解决方案。特例和一般情况。在这两种情况下,解决方案都与 OpenMP 并行地分两次遍历阵列。对于特殊情况,我在两次通过时都使用 SSE。对于一般情况,我只在第二次通过时使用它。
我的主要问题是在一般情况下如何在第一次通过时使用 SSE? 以下链接 simd-prefix-sum-on-intel-cpu显示字节的改进,但不是 32 位数据类型。
特殊情况之所以称为特殊是因为它要求数组采用特殊格式。例如,我们假设 float 组a
只有 16 个元素。那么如果数组像这样重新排列(结构数组到数组结构):
a[0] a[1] ...a[15] -> a[0] a[4] a[8] a[12] a[1] a[5] a[9] a[13]...a[3] a[7] a[11] a[15]
SSE 垂直和可用于两个 channel 。但是,只有当数组已经是特殊格式并且输出可以以特殊格式使用时,这才会有效。否则,必须对输入和输出都进行昂贵的重新排列,这将使其比一般情况慢得多。
也许我应该考虑使用不同的前缀和算法(例如二叉树)?
一般情况的代码:
void prefix_sum_omp_sse(double a[], double s[], int n) {
double *suma;
#pragma omp parallel
{
const int ithread = omp_get_thread_num();
const int nthreads = omp_get_num_threads();
#pragma omp single
{
suma = new double[nthreads + 1];
suma[0] = 0;
}
double sum = 0;
#pragma omp for schedule(static) nowait //first parallel pass
for (int i = 0; i<n; i++) {
sum += a[i];
s[i] = sum;
}
suma[ithread + 1] = sum;
#pragma omp barrier
#pragma omp single
{
double tmp = 0;
for (int i = 0; i<(nthreads + 1); i++) {
tmp += suma[i];
suma[i] = tmp;
}
}
__m128d offset = _mm_set1_pd(suma[ithread]);
#pragma omp for schedule(static) //second parallel pass with SSE as well
for (int i = 0; i<n/4; i++) {
__m128d tmp1 = _mm_load_pd(&s[4*i]);
tmp1 = _mm_add_pd(tmp1, offset);
__m128d tmp2 = _mm_load_pd(&s[4*i+2]);
tmp2 = _mm_add_pd(tmp2, offset);
_mm_store_pd(&s[4*i], tmp1);
_mm_store_pd(&s[4*i+2], tmp2);
}
}
delete[] suma;
}
最佳答案
这是我第一次回答我自己的问题,但这似乎很合适。基于 hirschhornsalz16 字节前缀和的答案 simd-prefix-sum-on-intel-cpu我想出了一个解决方案,可以在第一遍中对 4、8 和 16 个 32 位字使用 SIMD。
一般理论如下。对于 n
单词的顺序扫描,它需要 n
加法(n-1 扫描 n 个单词,再从上一组扫描的单词中进行加法)。然而,使用 SIMD n 个单词可以在 log2(n) 次加法和相等数量的移位中进行扫描,再加上一次加法和广播,以携带上一次 SIMD 扫描的结果。因此对于 n
的某些值,SIMD 方法将获胜。
让我们看看 SSE、AVX 和 AVX-512 的 32 位字:
4 32-bit words (SSE): 2 shifts, 3 adds, 1 broadcast sequential: 4 adds
8 32-bit words (AVX): 3 shifts, 4 adds, 1 broadcast sequential: 8 adds
16 32 bit-words (AVX-512): 4 shifts, 5 adds, 1 broadcast sequential: 16 adds
基于此,在 AVX-512 之前,SIMD 似乎无法用于扫描 32 位字。这还假设轮类和广播只能在一条指令中完成。这对 SSE 来说是正确的,但 not for AVX and maybe not even for AVX2 .
在任何情况下,我都将一些工作和测试代码放在一起,这些代码使用 SSE 进行前缀和。
inline __m128 scan_SSE(__m128 x) {
x = _mm_add_ps(x, _mm_castsi128_ps(_mm_slli_si128(_mm_castps_si128(x), 4)));
x = _mm_add_ps(x, _mm_castsi128_ps(_mm_slli_si128(_mm_castps_si128(x), 8)));
return x;
}
void prefix_sum_SSE(float *a, float *s, const int n) {
__m128 offset = _mm_setzero_ps();
for (int i = 0; i < n; i+=4) {
__m128 x = _mm_load_ps(&a[i]);
__m128 out = scan_SSE(x);
out = _mm_add_ps(out, offset);
_mm_store_ps(&s[i], out);
offset = _mm_shuffle_ps(out, out, _MM_SHUFFLE(3, 3, 3, 3));
}
请注意,scan_SSE
函数有两个加法 (_mm_add_ps) 和两个移位 (_mm_slli_si128)。强制转换仅用于使编译器满意,不会转换为指令。然后在 prefix_sum_SSE
中数组的主循环内,使用另一个添加和一个洗牌。与只有 4 个相加的顺序总和相比,总共有 6 个操作。
这是 AVX 的工作解决方案:
inline __m256 scan_AVX(__m256 x) {
__m256 t0, t1;
//shift1_AVX + add
t0 = _mm256_permute_ps(x, _MM_SHUFFLE(2, 1, 0, 3));
t1 = _mm256_permute2f128_ps(t0, t0, 41);
x = _mm256_add_ps(x, _mm256_blend_ps(t0, t1, 0x11));
//shift2_AVX + add
t0 = _mm256_permute_ps(x, _MM_SHUFFLE(1, 0, 3, 2));
t1 = _mm256_permute2f128_ps(t0, t0, 41);
x = _mm256_add_ps(x, _mm256_blend_ps(t0, t1, 0x33));
//shift3_AVX + add
x = _mm256_add_ps(x,_mm256_permute2f128_ps(x, x, 41));
return x;
}
void prefix_sum_AVX(float *a, float *s, const int n) {
__m256 offset = _mm256_setzero_ps();
for (int i = 0; i < n; i += 8) {
__m256 x = _mm256_loadu_ps(&a[i]);
__m256 out = scan_AVX(x);
out = _mm256_add_ps(out, offset);
_mm256_storeu_ps(&s[i], out);
//broadcast last element
__m256 t0 = _mm256_permute2f128_ps(out, out, 0x11);
offset = _mm256_permute_ps(t0, 0xff);
}
}
三个类次需要 7 个内在函数。广播需要 2 个内在函数。所以加上 4 个加法就是 13 个内在函数。对于 AVX2,移位只需要 5 个内在函数,因此总共需要 11 个内在函数。顺序和只需要8次加法。因此,AVX 和 AVX2 都可能对第一遍没有用。
编辑:
所以我最终对此进行了基准测试,结果出乎意料。 SSE 和 AVX 代码的速度大约是以下顺序代码的两倍:
void scan(float a[], float s[], int n) {
float sum = 0;
for (int i = 0; i<n; i++) {
sum += a[i];
s[i] = sum;
}
}
我猜这是由于指令级并行。
这样就回答了我自己的问题。在一般情况下,我成功地将 SIMD 用于 pass1。当我在我的 4 核 ivy 桥接系统上将它与 OpenMP 结合使用时,对于 512k float ,总加速约为 7。
关于c - 与 SSE 的并行前缀(累积)总和,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19494114/
在浏览可用的内在函数时,我注意到没有地方可以看到水平的addsub / subadd指令可用。在过时的3DNow中可用!扩展名,但是出于明显的原因,它的使用是不实际的。是什么原因导致SSE3扩展中无法
我需要在 SSE2 汇编程序中编写一些东西。 我所看到的都是内在因素。 我一直在寻找从内在函数到汇编器的转换表,但一直没有找到。 因为我不想玩猜谜游戏,有人可以给我一个链接,将这些可怕的内在函数转换为
我正在研究 SSE 并且是这里的新手。我正在尝试使用 shuffle 指令来随机播放一个 16 位向量,如下所示: 输入: 1 2 3 4 5 6 7 8 输出: 1 5 2 6 3 7 4 8 如何
我有一个用例,其中 x86 CPU 必须将 64 字节的数据写入内存已 mmapp 到用户空间的 PCIe 从属设备。截至目前,我使用 memcpy 来执行此操作,但事实证明它非常慢。我们可以使用像
我最近偶然发现了隐式 SSE/AVX 加载/存储。我认为这些是 GCC 的一些特殊扩展,但后来意识到它们也适用于 MSVC。 __m128 a = *(__m128*)data // same
仅将较高或较低 64 位从整数 SSE 寄存器移动到另一个的最快方法是什么?使用 SSE 4.1,可以使用单个 pblendw 来完成。指令(_mm_blend_epi16)。但是旧的 SSE 版本呢
SSE/AVX 寄存器可以被视为整数或浮点 BigNum。也就是说,人们可能会忽略车道的存在。是否存在一种简单的方法来利用这个观点并将这些寄存器单独或组合用作 BigNum?我问这个问题是因为从我对
我正在尝试比较 SSE float[4] 添加与标准 float[4] 添加。作为演示,我在使用和不使用 SSE 的情况下计算求和分量的总和: #include #include struct P
我处于以下情况: 我正在为不允许 SSE 指令的内核编写代码 我需要做浮点运算 我正在为 x86_64 平台编译 这是一个说明问题的代码示例: int main(int argc, char** ar
我处于以下情况: 我正在为不允许 SSE 指令的内核编写代码 我需要做浮点运算 我正在为 x86_64 平台编译 这是一个说明问题的代码示例: int main(int argc, char** ar
我正在尝试将用 SSE3 内在函数编写的代码转换为 NEON SIMD,但由于 shuffle 函数而卡住了。我查看了 GCC Intrinsic , ARM manuals和其他论坛,但一直无法找到
我正在尝试对一些代码进行 super 优化,我想加快速度的地方如下。 我想取一个 _m128 的点积运算 (_mm_dp_ps) 的答案,并将答案直接保存到寄存器中。但是,使用 _mm_store,这
我正在寻找 SSE 和 AVX 的 SIMD 数学库(最好是开源的)。我的意思是,例如,如果我有一个带有 8 个浮点值的 AVX 寄存器 v,我希望 sin(v) 一次返回所有八个值的 sin。 AM
假设我在 128 位变量/寄存器中有 16 个 ascii 字符(因此有 16 个 8 位数字)。我想创建一个位掩码,其中那些位将是高位,其位位置(索引)由这 16 个字符表示。 例如,如果由这 16
目前我正在使用 Visual C++ 内联汇编使用 SSE 嵌入一些核心功能;但是我意识到 x64 模式不支持内联汇编。 在 x64 架构中构建软件时如何使用 SSE? 最佳答案 在 C/C++ 中使
我正在寻找计算以下函数的有效方法: 输入:__m128i数据,uint8_t in; 输出: bool 值,指示 data 中的任何字节是否在 in 中。 我实际上是在使用它们为容量为 8 的字节实现
我正在寻找计算以下函数的有效方法: 输入:__m128i数据,uint8_t in; 输出: bool 值,指示 data 中的任何字节是否在 in 中。 我实际上是在使用它们为容量为 8 的字节实现
我正在尝试将最新消息拉入顶部页面。目前,每次收到新消息时,最新消息都会显示在下方。 if(typeof(EventSource)!=="undefined") { var source=new Ev
基本上我想做的是获取一个 __m128i 寄存器并将每个负字节的值设置为 -128 (0x80) 并且不更改任何正值。 确切的是: signed char __m128_as_char_arr[16]
有 2 个指针指向要加载到 xmm 寄存器中的 2 个未对齐的 8 字节 block 。如果可能,使用内在函数。如果可能的话,不使用辅助寄存器。没有pinsrd。 (SSSE核心2) 最佳答案 来自
我是一名优秀的程序员,十分优秀!