Ner*_*rva 5 c++ assembly simd openmp computer-architecture
我在这做错了什么?我得到4个零而不是:
2
4
6
8
Run Code Online (Sandbox Code Playgroud)
我也想修改我的.asm函数,以便在这里运行更长的向量,因为我只使用了一个带有四个元素的向量,这样我就可以在没有带有SIMD 256位寄存器的循环的情况下对该向量求和.
#include <iostream>
#include <chrono>
extern "C" double *addVec(double *C, double *A, double *B, size_t &N);
int main()
{
size_t N = 1 << 2;
size_t reductions = N / 4;
double *A = (double*)_aligned_malloc(N*sizeof(double), 32);
double *B = (double*)_aligned_malloc(N*sizeof(double), 32);
double *C = (double*)_aligned_malloc(N*sizeof(double), 32);
for (size_t i = 0; i < N; i++)
{
A[i] = double(i + 1);
B[i] = double(i + 1);
}
auto start = std::chrono::high_resolution_clock::now();
double *out = addVec(C, A, B, reductions);
auto finish = std::chrono::high_resolution_clock::now();
for (size_t i = 0; i < N; i++)
{
std::cout << out[i] << std::endl;
}
std::cout << "\n\n";
std::cout << std::chrono::duration_cast<std::chrono::nanoseconds>(finish - start).count() << " ns\n";
std::cin.get();
_aligned_free(A);
_aligned_free(B);
_aligned_free(C);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
.data
; C -> RCX
; A -> RDX
; B -> r8
; N -> r9
.code
addVec proc
;xor rbx, rbx
align 16
;aIn:
vmovapd ymm0, ymmword ptr [rdx]
;vmovapd ymm1, ymmword ptr [rdx + rbx + 4]
vmovapd ymm2, ymmword ptr [r8]
;vmovapd ymm3, ymmword ptr [r8 + rbx + 4]
vaddpd ymm0, ymm2, ymm3
vmovapd ymmword ptr [rcx], ymm3
;inc rbx
;cmp rbx, qword ptr [r9]
;jl aIn
mov rax, rcx ; return the address of the output vector
ret
addVec endp
end
Run Code Online (Sandbox Code Playgroud)
另外,我想补充一些其他说明:
如果我在没有在汇编函数中放置循环的情况下执行以下操作会怎么样?:
#pragma openmp parallel for
for (size_t i = 0; i < reductions; i++)
addVec(C + i, A + i, B + i)
这会分叉coreNumber + hyperThreading线程,每个线程执行SIMD添加四个双?那么每个循环总共4*coreNumber加倍?我不能在这里添加hyperThreading吗?
我可以更新吗?:
.data
;// C -> RCX
;// A -> RDX
;// B -> r8
.code
addVec proc
; One cycle 8 micro-op
vmovapd ymm0, ymmword ptr [rdx] ; 1 port
vmovapd ymm1, ymmword ptr [rdx + 32]; 1 port
vmovapd ymm2, ymmword ptr [r8] ; 1 port
vmovapd ymm3, ymmword ptr [r8 + 32] ; 1 port
vfmadd231pd ymm0, ymm2, ymm4 ; 1 port
vfmadd231pd ymm1, ymm3, ymm4 ; 1 port
vmovapd ymmword ptr [rcx], ymm0 ; 1 port
vmovapd ymmword ptr [rcx + 32], ymm1; 1 port
; Return the address of the output vector
mov rax, rcx ; 1 port ?
ret
addVec endp
end
Run Code Online (Sandbox Code Playgroud)
或者只是这个'因为我会超过你告诉我的六个端口?
.data
;// C -> RCX
;// A -> RDX
;// B -> r8
.code
addVec proc
;align 16
; One cycle 5 micro-op ?
vmovapd ymm0, ymmword ptr [rdx] ; 1 port
vmovapd ymm1, ymmword ptr [r8] ; 1 port
vfmadd231pd ymm0, ymm1, ymm2 ; 1 port
vmovapd ymmword ptr [rcx], ymm0 ; 1 port
; Return the address of the output vector
mov rax, rcx ; 1 port ?
ret
addVec endp
end
Run Code Online (Sandbox Code Playgroud)
您的代码得到错误结果的原因是您的程序集中的语法向后.
您正在使用英特尔语法,其中目标应位于源之前.所以在你原来的.asm代码中你应该改变
vaddpd ymm0, ymm2, ymm3
Run Code Online (Sandbox Code Playgroud)
至
vaddpd ymm3, ymm2, ymm0
Run Code Online (Sandbox Code Playgroud)
一种看待这种情况的方法是使用内在函数,然后查看反汇编.
extern "C" double *addVec(double * __restrict C, double * __restrict A, double * __restrict B, size_t &N) {
__m256d x = _mm256_load_pd((const double*)A);
__m256d y = _mm256_load_pd((const double*)B);
__m256d z = _mm256_add_pd(x,y);
_mm256_store_pd((double*)C, z);
return C;
}
Run Code Online (Sandbox Code Playgroud)
在Linux上使用GCC来解释g++ -S -O3 -mavx -masm=intel -mabi=ms foo.cpp:
vmovapd ymm0, YMMWORD PTR [rdx]
mov rax, rcx
vaddpd ymm0, ymm0, YMMWORD PTR [r8]
vmovapd YMMWORD PTR [rcx], ymm0
vzeroupper
ret
Run Code Online (Sandbox Code Playgroud)
该vaddpd ymm0, ymm0, YMMWORD PTR [rdx]指令将加载和加法融合到一个融合微操作中.当我在你的代码中使用该函数时,它获得2,4,6,8.
可以看到总结两个阵列的源代码x和y并写出到一个数组z在L1-存储器带宽-50-滴在-使用-地址-其中-不同逐4096效率.这使用内在函数并展开八次.使用gcc -S或解密代码objdump -d.另一个执行几乎完全相同的事情并且在汇编中编写的源代码是获取峰值带宽 - 仅在l1-cache-only-getting-62.在文件triad_fma_asm.asm中将行更改pi: dd 3.14159为pi: dd 1.0.这两个示例都使用单个浮点,因此如果您想要加倍,则必须进行必要的更改.
您的其他问题的答案是:
请注意,每个内核的寄存器数量远远多于您可以直接编程的逻辑寄存器.
见上文
自2006年以来,通过Haswell的Core2处理器每个时钟最多可以处理4个μop.然而,使用称为微操作融合和宏操作融合的两种技术,Haswell可以在每个时钟周期实现6个微操作.
微操作融合可以将例如负载和加法融合到一个所谓的融合微操作中,但是每个微操作仍然需要其自己的端口.宏操作融合可以融合例如标量添加和跳转到一个只需要一个端口的微操作.宏观操作融合基本上是两个一个.
Haswell有八个端口.您可以使用这样的七个端口在一个时钟周期内获得六个微操作.
256-load + 256-FMA //one fused µop using two ports
256-load + 256-FMA //one fused µop using two ports
256-store //one µop using two ports
64-bit add + jump //one µop using one port
Run Code Online (Sandbox Code Playgroud)
因此实际上Haswell的每个内核可以处理16个双精度数(每个FMA增加4个乘法和4个),两个256负载,一个256位存储,以及一个64位的加法和分支,在一个时钟周期内完成.在这个问题中,获得峰值带宽一直在l1-cache-only-getting-62,我在理论上使用六个端口在一个时钟周期内获得了五个微操作.然而,在Haswell的实践中,这很难实现.
对于您的特定操作,它读取两个数组并写入一个,它受每个时钟周期的两次读取限制,因此每个时钟周期只能发出一个FMA.所以它能做的最好的是每个时钟周期四个双打.
但是,让我告诉你英特尔不希望人们谈论太多的秘密.大多数操作都受内存带宽限制,并且无法从并行化中获益.这包括您问题中的操作.因此,尽管英特尔每隔几年就会推出新技术(例如AVX,FMA,AVX512,内核数量翻倍),每次声称摩尔定律在实践中得到的性能翻倍,平均效益是线性的而不是指数的它已经有好几年了.