从第二个导数计算的曲线的SIMD优化
SIMD optimization of a curve computed from the second derivative
这个问题确实是一个好奇。
我正在将例程转换为SIMD指令(我是SIMD编程的新手),并且在以下代码上遇到了麻烦:
// args:
uint32_t phase_current;
uint32_t phase_increment;
uint32_t phase_increment_step;
for (int i = 0; i < blockSize; ++i)
{
USEFUL_FUNC(phase_current);
phase_increment += phase_increment_step;
phase_current += phase_increment;
}
问题:假设USEFUL_FUNC
具有SIMD实现,我只是试图计算一个用于处理的phase_current
的正确向量,那么处理phase_current
的正确方法是什么?
反过来,功能编程fold
类似于实现将同样有用,因为我试图了解如何为了优化而尝试优化数据依赖性。
最后,如果您可以推荐一些文学作品,请这样做。不确定如何搜索此主题。
,因此您只是在寻找一种生成4个speast_current值向量的方法,您可以将其传递给arg to to nutary函数。
tl:dr :设置初始向量的增量和步骤,以便每个向量元素以4的序列跨过序列,为您提供phase_current[i+0..i+3]
的向量,只有两个矢量添加操作(垂直,不,不,水平的)。此串行依赖性是您可以用代数/MATH 。
这有点像一个前缀-SUM(您可以使用log2(vector_width)
Shuffle 添加带有vector_width
元素的向量的操作。总和一个数组的区域,然后将结果组合在一起,并使每个线程偏移其目标阵列的区域(该区域的第一个元素的总和总数。也请参见多线程的链接问题。
,但是您有一个巨大的简化,即phase_increment_step
(您想要的值的第二个衍生物)是常数。我假设USEFUL_FUNC(phase_current);
按值乘以其ARG,而不是通过非const引用,因此对phase_current
的唯一修改是循环中的+=
。并且useful_func
不能以某种方式突变增量或增量。
实现此目的的一个选项只是在SIMD向量的4个单独的元素中独立运行标量算法,每次都被1个迭代抵消。使用Integer Adds,尤其是在Intel CPU上,在Intel CPU上,Vector-Integer添加延迟仅为1个周期,运行4次运行时间的迭代很便宜,我们可以在USEFUL_FUNC
的调用之间做到这一点。这将是一种生成向量输入到有用的_func的方法的方法(假设Simd Integer添加添加与标量整数添加一样便宜,如果我们受到数据依赖的限制,这主要是正确的,这是正确的。)。
上面的方法更一般性,对于这个问题的变化可能很有用,在有一个真正的串行依赖性,我们无法通过简单的数学来廉价消除。
如果我们很聪明,我们可以比前缀总和或蛮力一次更好地运行4个序列。理想情况下,我们可以在值的序列中得出一种封闭形式的方式(或SIMD向量宽度是什么,对于USEFUL_FUNC
的多个累加器)。
总结step
,step*2
,step*3
的序列,...将为我们提供一个恒定的时间高斯的封闭形式公式,以达到n
:sum(1..n) = n*(n+1)/2
的整数之和。该序列为0、1、3、6、10、15、21、28,...(https://oeis.org/a000217)。(我已经考虑了初始phase_increment
)。
这个序列中的技巧是4。(n+4)*(n+5)/2 - n*(n+1)/2
简化为4*n + 10
。再次采用派生,我们得到4.但是要在第二个积分中走4个步骤,我们有4*4 = 16
。因此,我们可以维护一个向量phase_increment
,并使用 16*phase_increment_step
的向量添加SIMD添加。
我不确定自己的阶级推理权利权利(额外的4个要给16的因素有点令人惊讶)。在媒介序列中制定正确的公式并进行第一和二维,这很清楚它如何运行:
// design notes, working through the first couple vectors
// to prove this works correctly.
S = increment_step (constant)
inc0 = increment initial value
p0 = phase_current initial value
// first 8 step-increases:
[ 0*S, 1*S, 2*S, 3*S ]
[ 4*S, 5*S, 6*S, 7*S ]
// first vector of 4 values:
[ p0, p0+(inc0+S), p0+(inc0+S)+(inc0+2*S), p0+(inc0+S)+(inc0+2*S)+(inc0+3*S) ]
[ p0, p0+inc0+S, p0+2*inc0+3*S, p0+3*inc0+6*S ] // simplified
// next 4 values:
[ p0+4*inc0+10*S, p0+5*inc0+15*S, p0+6*inc0+21*S, p0+7*inc0+28*S ]
使用此和较早的4*n + 10
公式:
// first 4 vectors of of phase_current
[ p0, p0+1*inc0+ 1*S, p0+2*inc0+3*S, p0+ 3*inc0+ 6*S ]
[ p0+4*inc0+10*S, p0+5*inc0+15*S, p0+6*inc0+21*S, p0+ 7*inc0+28*S ]
[ p0+8*inc0+36*S, p0+9*inc0+45*S, p0+10*inc0+55*S, p0+11*inc0+66*S ]
[ p0+12*inc0+78*S, p0+13*inc0+91*S, p0+14*inc0+105*S, p0+15*inc0+120*S ]
first 3 vectors of phase_increment (subtract consecutive phase_current vectors):
[ 4*inc0+10*S, 4*inc0 + 14*S, 4*inc0 + 18*S, 4*inc0 + 22*S ]
[ 4*inc0+26*S, 4*inc0 + 30*S, 4*inc0 + 34*S, 4*inc0 + 38*S ]
[ 4*inc0+42*S, 4*inc0 + 46*S, 4*inc0 + 50*S, 4*inc0 + 54*S ]
first 2 vectors of phase_increment_step:
[ 16*S, 16*S, 16*S, 16*S ]
[ 16*S, 16*S, 16*S, 16*S ]
Yes, as expected, a constant vector works for phase_increment_step
因此,我们可以使用Intel的SSE/AVX Intrinsics 编写代码:
#include <stdint.h>
#include <immintrin.h>
void USEFUL_FUNC(__m128i);
// TODO: more efficient generation of initial vector values
void double_integral(uint32_t phase_start, uint32_t phase_increment_start, uint32_t phase_increment_step, unsigned blockSize)
{
__m128i pstep1 = _mm_set1_epi32(phase_increment_step);
// each vector element steps by 4
uint32_t inc0=phase_increment_start, S=phase_increment_step;
__m128i pincr = _mm_setr_epi32(4*inc0 + 10*S, 4*inc0 + 14*S, 4*inc0 + 18*S, 4*inc0 + 22*S);
__m128i phase = _mm_setr_epi32(phase_start, phase_start+1*inc0+ 1*S, phase_start+2*inc0+3*S, phase_start + 3*inc0+ 6*S );
//_mm_set1_epi32(phase_start); and add.
// shuffle to do a prefix-sum initializer for the first vector? Or SSE4.1 pmullo by a vector constant?
__m128i pstep_stride = _mm_slli_epi32(pstep1, 4); // stride by pstep * 16
for (unsigned i = 0; i < blockSize; ++i) {
USEFUL_FUNC(phase);
pincr = _mm_add_epi32(pincr, pstep_stride);
phase = _mm_add_epi32(phase, pincr);
}
}
进一步阅读:有关SIMD的更多信息,但主要是X86 SSE/AVX,请参见https://stackoverflow.com/tags/sse/sse/info,尤其是Insomniac Games的Simd Slides(GDC 2015)关于如何考虑SIMD的内容,以及如何布置您的数据,以便您可以使用它。
我唯一能想到的是水平添加。想象一下,您的__m128i向量{pc,0,pi,pis}。然后首先将其纳入{pc,pi pis},第二个hadd将其纳入pc + pi + pis
。
HADD一次在两个__M128i上运行,因此可以加快加速。
但交织的指示使管道总是满是琐碎的。链接到HADD:https://msdn.microsoft.com/en-us/library/bb531452(v = vs.120).aspx
让我添加链接到非常有用的讨论wrt hadd for浮子。许多代码和结论可以直接应用于Integer HADD:在x86
- 空基优化子对象的地址
- 关闭||运算符优化
- 如何解决gcc编译器优化导致的centos双编译器设置中的分段错误
- 返回值优化:显式移动还是隐式
- 人脸跟踪arduino代码的优化
- 使用仅使用一次的变量调用的复制构造函数.这可能是通过调用move构造函数进行编译器优化的情况吗
- 纯函数,为什么没有优化
- 为什么大多数 pair 实现默认不使用压缩(空基优化)?
- 如何以优化的方式同时迭代两个间距不相等的数组
- 小字符串优化(调试与发布模式)
- 浮点定向舍入和优化
- Visual Studio 调试优化如何工作?
- 为什么开关的优化方式与 c/c++ 中的链接不同?
- 线性优化目标函数中的绝对值
- GCC 会优化内联访问器吗?
- gcc 如何优化此循环?
- 如何防止 CUDA-GDB 中的<优化输出>值
- CGAL:如何创建填充边界曲线的曲面网格?
- 从第二个导数计算的曲线的SIMD优化
- 如何优化此 S 曲线函数