英特尔汇编与内部函数,AVX
Intel assembly vs Intrinsics, AVX
我有一个简单的矢量-矢量加法算法(c = a + b * lambda(,使用AVX指令用英特尔汇编编写。 这是我的代码:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Dense to dense
;; Uses cache
;; AVX
;; Without tolerances
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
global _denseToDenseAddAVX_cache_64_linux
_denseToDenseAddAVX_cache_64_linux:
push rbp
mov rbp, rsp
; rdi: address1
; rsi: address2
; rdx: address3
; rcx: count
; xmm0: lambda
mov rax, rcx
shr rcx, 3
and rax, 0x07
vzeroupper
vmovupd ymm5, [abs_mask]
sub rsp, 8
vmovlpd [rbp - 8], xmm0
vbroadcastsd ymm7, [rbp - 8]
vmovapd ymm6, ymm7
cmp rcx, 0
je after_loop_denseToDenseAddAVX_cache_64_linux
start_denseToDenseAddAVX_cache_64_linux:
vmovapd ymm0, [rdi] ; a
vmovapd ymm1, ymm7
vmulpd ymm1, [rsi] ; b
vaddpd ymm0, ymm1 ; ymm0 = c = a + b * lambda
vmovapd [rdx], ymm0
vmovapd ymm2, [rdi + 32] ; a
vmovapd ymm3, ymm6
vmulpd ymm3, [rsi + 32] ; b
vaddpd ymm2, ymm3 ; ymm2 = c = a + b * lambda
vmovapd [rdx + 32], ymm2
add rdi, 64
add rsi, 64
add rdx, 64
loop start_denseToDenseAddAVX_cache_64_linux
after_loop_denseToDenseAddAVX_cache_64_linux:
cmp rax, 0
je end_denseToDenseAddAVX_cache_64_linux
mov rcx, rax
last_loop_denseToDenseAddAVX_cache_64_linux:
vmovlpd xmm0, [rdi] ; a
vmovapd xmm1, xmm7
vmulsd xmm1, [rsi] ; b
vaddsd xmm0, xmm1 ; xmm0 = c = a + b * lambda
vmovlpd [rdx], xmm0
add rdi, 8
add rsi, 8
add rdx, 8
loop last_loop_denseToDenseAddAVX_cache_64_linux
end_denseToDenseAddAVX_cache_64_linux:
mov rsp, rbp
pop rbp
ret
人们经常建议我使用英特尔内部函数,因为它更好、更安全。现在我已经实现了这个算法,如下所示:
void denseToDenseAddAVX_cache(const double * __restrict__ a,
const double * __restrict__ b,
double * __restrict__ c,
size_t count, double lambda) {
const size_t firstCount = count / 8;
const size_t rem1 = count % 8;
int i;
__m256d mul = _mm256_broadcast_sd(&lambda);
for (i = 0; i < firstCount; i++) {
// c = a + b * lambda
__m256d dataA1 = _mm256_load_pd(&a[i * 8]);
__m256d dataC1 = _mm256_add_pd(dataA1, _mm256_mul_pd(_mm256_load_pd(&b[i * 8]), mul ));
_mm256_store_pd(&c[i * 8], dataC1);
__m256d dataA2 = _mm256_load_pd(&a[i * 8 + 4]);
__m256d dataC2 = _mm256_add_pd(dataA2, _mm256_mul_pd(_mm256_load_pd(&b[i * 8 + 4]), mul ));
_mm256_store_pd(&c[i * 8 + 4], dataC2);
}
const size_t secondCount = rem1 / 4;
const size_t rem2 = rem1 % 4;
if (secondCount) {
__m256d dataA = _mm256_load_pd(&a[i * 8]);
__m256d dataC = _mm256_add_pd(dataA, _mm256_mul_pd(_mm256_load_pd(&b[i * 8]), mul ));
_mm256_store_pd(&c[i * 8], dataC);
i += 4;
}
for (; i < count; i++) {
c[i] = a[i] + b[i] * lambda;
}
}
我的问题是汇编版本比第二个快两倍。c++ 版本有什么问题?
几件事。
-
我认为这是最重要的一个。程序集代码使用指针算术。您的C++代码没有,您首先计算索引,然后获取地址。编译器通常会优化指针数学,但这并不可靠,您最好在C++中使用相同的指针数学。更糟糕的是,像 &a[i * 8 + 4] 这样的东西需要多个整数指令。以字节为单位的结果是 a+i*64+32,而 x86 指令只能按因子 2、4 或 8 自由缩放整数。因此,编译器必须发出左移,然后加法来计算地址。此问题使循环正文中的指令数加倍。
-
C++对循环计数器使用有符号 32 位整数,程序集代码使用无符号 64 位整数。对于性能关键型代码,最好在循环计数器的C++中使用
size_t
。顺便说一句,如果您在C++编译器中设置了"警告为错误"设置,它将拒绝编译,说"有符号/无符号不匹配"之类的内容。 -
您在C++中有冗余负载。CPU 可以用一条指令进行数学运算 + 一次加载。要执行与汇编相同的操作,请不要使用
_mm256_load_pd
,将指针从const double *
转换为const __m256d*
下面是稍微简化的示例:
void denseToDenseAddAVX( const double *a, const double *b, double *c, size_t count, double lambda )
{
assert( 0 == (size_t)( a ) % 32 );
assert( 0 == (size_t)( b ) % 32 );
assert( 0 == (size_t)( c ) % 32 );
const double* const aEnd = a + count;
const double* const aEndAligned = a + ( ( count / 4 ) * 4 );
const __m256d mul = _mm256_set1_pd( lambda );
while( a < aEndAligned )
{
const __m256d* const av = ( const __m256d* )a;
const __m256d* const bv = ( const __m256d* )b;
const __m256d cv = _mm256_add_pd( *av, _mm256_mul_pd( *bv, mul ) );
_mm256_store_pd( c, cv );
a += 4;
b += 4;
c += 4;
}
while( a < aEnd )
{
*c = ( *a ) + ( *b ) * lambda;
a++;
b++;
c++;
}
}
相关文章:
- C++ SSE 内部函数:将结果存储在变量中
- C++代码停止工作错误使用cout内部函数
- 为什么从具有较大阵列的 SIMD 内部函数中获得的相对加速比标量更大?
- 使用英特尔内部函数 (AVX) 中的混合说明
- 英特尔汇编与内部函数,AVX
- 使用SSE内部函数复制少量数据时出现问题
- 在为函数编写单元测试时,我应该模拟所做的内部函数调用吗?
- 用于平铺矩阵乘法的 AVX 内部函数
- 是否可以使用类的析构函数内部函数来重置值?
- 我在理解 AVX 随机内部函数如何为 8 位时遇到一些问题
- 无法执行内部函数 strlen
- COUT 内部函数调用的顺序
- GCC(通过 CUDA)内部函数的编译器错误,但我没有使用任何
- C++ ld 链接器 --wrap 选项不适用于内部函数调用
- std::AVX内部函数数组
- 性能 AVX/SSE 程序集与内部函数
- 在 AVX 内部函数 vec 中填充恒定浮点数
- g++-4.8 中缺少 AVX 日志内部函数 (_mm256_log_ps)
- 强制AVX内部函数使用SSE指令
- 使用sse和avx内部函数将一组打包的单值添加到一个值中