如何在现代x86-64 Intel CPU上实现每个周期4个浮点运算(双精度)的理论峰值性能?
据我所知,SSE 需要三个周期,add而mul大多数现代Intel CPU需要五个周期才能完成(参见例如Agner Fog的"指令表").由于流水线操作,add如果算法具有至少三个独立的求和,则每个周期可以获得一个吞吐量.因为打包addpd和标量addsd版本都是如此,并且SSE寄存器可以包含两个,double每个周期的吞吐量可以高达两个触发器.
此外,似乎(虽然我没有看到任何适当的文档)add并且mul可以并行执行,给出每个周期四个触发器的理论最大吞吐量.
但是,我无法使用简单的C/C++程序复制该性能.我最好的尝试导致大约2.7个翻牌/周期.如果有人可以贡献一个简单的C/C++或汇编程序,它可以表现出非常高兴的峰值性能.
我的尝试:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <sys/time.h>
double stoptime(void) {
struct timeval t;
gettimeofday(&t,NULL);
return (double) t.tv_sec + t.tv_usec/1000000.0;
}
double addmul(double add, double mul, int ops){
// Need to initialise differently otherwise compiler might optimise away
double sum1=0.1, sum2=-0.1, sum3=0.2, sum4=-0.2, sum5=0.0;
double mul1=1.0, mul2= 1.1, mul3=1.2, mul4= 1.3, …Run Code Online (Sandbox Code Playgroud) 这个问题在我的问题上继续(根据神秘的建议):
继续我的问题,当我使用压缩指令而不是标量指令时,使用内在函数的代码看起来非常相似:
for(int i=0; i<size; i+=16) {
y1 = _mm_load_ps(output[i]);
…
y4 = _mm_load_ps(output[i+12]);
for(k=0; k<ksize; k++){
for(l=0; l<ksize; l++){
w = _mm_set_ps1(weight[i+k+l]);
x1 = _mm_load_ps(input[i+k+l]);
y1 = _mm_add_ps(y1,_mm_mul_ps(w,x1));
…
x4 = _mm_load_ps(input[i+k+l+12]);
y4 = _mm_add_ps(y4,_mm_mul_ps(w,x4));
}
}
_mm_store_ps(&output[i],y1);
…
_mm_store_ps(&output[i+12],y4);
}
Run Code Online (Sandbox Code Playgroud)
这个内核的测量性能是每个周期大约5.6个FP操作,虽然我预计它将是标量版本性能的4倍,即每个周期4.1,6 = 6,4 FP操作.
考虑到权重因素的移动(感谢指出这一点),时间表如下:

看起来调度没有改变,尽管在操作之后有一条额外的指令movss将标量权重值移动到XMM寄存器然后用于shufps在整个向量中复制这个标量值.似乎权重向量已经准备好用于mulps考虑从加载到浮点域的切换延迟,因此这不应该产生任何额外的延迟.
此内核中使用的movaps(对齐,打包的移动)addps和mulps指令(使用汇编代码检查)与其标量版本具有相同的延迟和吞吐量,因此这不会产生任何额外的延迟.
有没有人知道每8个周期的额外周期花费在哪里,假设这个内核可以获得的最大性能是每个周期6.4个FP运算并且每个周期运行5.6个FP运算?
顺便说一下,这是实际装配的样子:
…
Block x:
movapsx (%rax,%rcx,4), %xmm0
movapsx 0x10(%rax,%rcx,4), %xmm1
movapsx 0x20(%rax,%rcx,4), %xmm2
movapsx 0x30(%rax,%rcx,4), %xmm3
movssl …Run Code Online (Sandbox Code Playgroud) 我正在寻找一种更快,更棘手的方法来将C中的两个4x4矩阵相乘.我目前的研究主要集中在具有SIMD扩展的x86-64汇编上.到目前为止,我已经创建了一个函数,比一个简单的C实现快6倍,这超出了我对性能改进的期望.不幸的是,只有在没有使用优化标志进行编译时(GCC 4.7),这种情况才会成立.随着-O2,C变得更快,我的努力变得毫无意义.
我知道现代编译器利用复杂的优化技术来实现几乎完美的代码,通常比巧妙的手工装配更快.但在少数性能关键的情况下,人类可能会尝试使用编译器争取时钟周期.特别是,当一些支持现代ISA的数学可以被探索时(就像我的情况一样).
我的函数如下(AT&T语法,GNU汇编程序):
.text
.globl matrixMultiplyASM
.type matrixMultiplyASM, @function
matrixMultiplyASM:
movaps (%rdi), %xmm0 # fetch the first matrix (use four registers)
movaps 16(%rdi), %xmm1
movaps 32(%rdi), %xmm2
movaps 48(%rdi), %xmm3
xorq %rcx, %rcx # reset (forward) loop iterator
.ROW:
movss (%rsi), %xmm4 # Compute four values (one row) in parallel:
shufps $0x0, %xmm4, %xmm4 # 4x 4FP mul's, 3x 4FP add's 6x mov's per row,
mulps %xmm0, %xmm4 # expressed in four sequences of 5 instructions,
movaps …Run Code Online (Sandbox Code Playgroud) x86-64的SSE指令(向量指令)在哪里优于正常指令.因为我所看到的是,执行SSE指令所需的频繁加载和存储会使由于向量计算而产生的任何增益无效.那么有人可以给我一个示例SSE代码,它比普通代码表现更好.
也许是因为我分别传递了每个参数,就像这样......
__m128i a = _mm_set_epi32(pa[0], pa[1], pa[2], pa[3]);
__m128i b = _mm_set_epi32(pb[0], pb[1], pb[2], pb[3]);
__m128i res = _mm_add_epi32(a, b);
for( i = 0; i < 4; i++ )
po[i] = res.m128i_i32[i];
Run Code Online (Sandbox Code Playgroud)
有没有办法我可以一次性传递所有4个整数,我的意思是一次性传递整个128个字节pa?并分配res.m128i_i32给po一气呵成?
c ×4
assembly ×3
optimization ×2
sse ×2
architecture ×1
c++ ×1
instructions ×1
intel ×1
performance ×1
x86-64 ×1