我有以下代码来计算 Hadamard 变换。现在,hadamard函数是我的程序的瓶颈。你认为有任何加速的潜力吗?也许使用 AVX2 指令?典型的输入大小约为 512 或 1024。
贝斯特,汤姆
·#include <stdio.h>
void hadamard(double *p, size_t len) {
double tmp = 0.0;
if(len == 2) {
tmp = p[0];
p[0] = tmp + p[1];
p[1] = tmp - p[1];
} else {
hadamard(p, len/2);
hadamard(p+len/2, len/2);
for(int i = 0; i < len/2; i++) {
tmp = p[i];
p[i] = tmp + p[i+len/2];
p[i+len/2] = tmp - p[i+len/2];
}
}
}
int main(int argc, char* argv[]) {
double a[] = {1.0, 2.0, 3.0, 4.0};
hadamard(a, 4);
}
这是一个概念验证实现,基于快速沃尔什-哈达玛变换,具有优化的首遍。 使用 clang-3.3 或更高版本编译良好,使用 clang-4.0 或更高版本提供不错的结果(需要 -O2 才能正确内联相关函数)。如果你没有FMA,你需要在hadamard4
中用-0.0
hada2_
的下两个元素进行异化(并使用普通_mm256_add_pd
)。
我检查的所有 gcc 版本都需要通过手动加载/存储内部函数替换memcpy
以获得类似的结果。
此外,我把处理案件len<16
作为练习。并且可能值得实施一个hadamard32
,也许是一个类似于hadamard16
hadamard64
,以更好地使用可用的寄存器并减少内存访问。C++这可以通过递归模板实现来完成。
#include <immintrin.h> // avx+fma
#include <assert.h> // assert
#include <string.h> // memcpy
inline __m256d hadamard4(__m256d x0123)
{
__m256d x1032 = _mm256_permute_pd(x0123, 5); // [x1, x0, x3, x2]
__m256d hada2 = _mm256_addsub_pd(x1032,x0123); // [x0+x1, x0-x1, x2+x3, x2-x3]
__m256d hada2_= _mm256_permute2f128_pd(hada2, hada2, 1); // [x2+x3, x2-x3, x0+x1, x0-x1]
// if no FMA is available, this can be done with xoring and adding:
__m256d res = _mm256_fmadd_pd(hada2_, _mm256_set_pd(1.0, 1.0, -1.0, -1.0), hada2);
return res;
}
inline void hadamard8(__m256d data[2])
{
__m256d a = hadamard4(data[0]);
__m256d b = hadamard4(data[1]);
data[0] = _mm256_add_pd(a,b);
data[1] = _mm256_sub_pd(a,b);
}
inline void hadamard16(__m256d data[4])
{
hadamard8(data+0);
hadamard8(data+2);
for(int i=0; i<2; ++i)
{
__m256d tmp = data[i];
data[i] = _mm256_add_pd(tmp, data[i+2]);
data[i+2]= _mm256_sub_pd(tmp, data[i+2]);
}
}
void hadamard(double* p, size_t len)
{
assert((len&(len-1))==0); // len must be power of 2
assert(len>=16); // TODO implement fallback for smaller sizes ...
// first pass: hadamard of 16 values each
for(size_t i=0; i<len; i+=16)
{
__m256d data[4];
memcpy(data, p+i, sizeof(data)); // should get optimized to 4x vmovupd
hadamard16(data);
memcpy(p+i, data, sizeof(data)); // should get optimized to 4x vmovupd
}
for(size_t h=32; h<len; h*=2)
{
for(size_t i=0; i<len; i+=2*h)
{
for(size_t j=i; j<i+h; j+=4)
{
__m256d x = _mm256_loadu_pd(p+j);
__m256d y = _mm256_loadu_pd(p+j+h);
_mm256_storeu_pd(p+j, _mm256_add_pd(x,y));
_mm256_storeu_pd(p+j+h, _mm256_sub_pd(x,y));
}
}
}
}
基准测试也是一种练习;-)
免责声明:我没有测试这个。看着它,我可能在_mm256_fmadd_pd
指令中混淆了hada2_
和hada2
......