如何在现代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) 为什么,在硬件执行操作的最低级别和所涉及的一般底层操作(即:运行代码时所有编程语言的实际实现的一般性),矢量化通常比循环更快?
当使用矢量化时,计算机在循环时做了什么(我说的是计算机执行的实际计算,而不是程序员编写的计算),或者它有什么不同的做法?
我一直无法说服自己为什么差异应该如此重要.我可能会说服矢量化代码在某处削减一些循环开销,但计算机仍然必须执行相同数量的操作,不是吗?例如,如果我们将大小为N的向量乘以标量,我们将使用N次乘法执行任一方式,不是吗?
我已经了解到一些Intel/AMD CPU可以同时进行多次复用并添加SSE/AVX:
每个周期的FLOPS用于沙桥和haswell SSE2/AVX/AVX2.
我想知道如何在代码中做到最好,我也想知道它是如何在CPU内部完成的.我的意思是超标量架构.假设我想做一个很长的总和,如下面的SSE:
//sum = a1*b1 + a2*b2 + a3*b3 +... where a is a scalar and b is a SIMD vector (e.g. from matrix multiplication)
sum = _mm_set1_ps(0.0f);
a1 = _mm_set1_ps(a[0]);
b1 = _mm_load_ps(&b[0]);
sum = _mm_add_ps(sum, _mm_mul_ps(a1, b1));
a2 = _mm_set1_ps(a[1]);
b2 = _mm_load_ps(&b[4]);
sum = _mm_add_ps(sum, _mm_mul_ps(a2, b2));
a3 = _mm_set1_ps(a[2]);
b3 = _mm_load_ps(&b[8]);
sum = _mm_add_ps(sum, _mm_mul_ps(a3, b3));
...
Run Code Online (Sandbox Code Playgroud)
我的问题是如何将其转换为同时乘法并添加?数据可以依赖吗?我的意思是CPU可以_mm_add_ps(sum, _mm_mul_ps(a1, b1))同时执行还是在乘法中使用的寄存器和add必须是独立的?
最后,这如何适用于FMA(与Haswell)?是_mm_add_ps(sum, _mm_mul_ps(a1, b1))自动转换为单个FMA指令还是微操作?
我用AVX一次计算八个点产品.在我目前的代码中,我做了类似的事情(在展开之前):
常春藤桥/桑迪桥
__m256 areg0 = _mm256_set1_ps(a[m]);
for(int i=0; i<n; i++) {
__m256 breg0 = _mm256_load_ps(&b[8*i]);
tmp0 = _mm256_add_ps(_mm256_mul_ps(arge0,breg0), tmp0);
}
Run Code Online (Sandbox Code Playgroud)
Haswell的
__m256 areg0 = _mm256_set1_ps(a[m]);
for(int i=0; i<n; i++) {
__m256 breg0 = _mm256_load_ps(&b[8*i]);
tmp0 = _mm256_fmadd_ps(arge0, breg0, tmp0);
}
Run Code Online (Sandbox Code Playgroud)
我需要多少次为每个案例展开循环以确保最大吞吐量?
对于使用FMA3的Haswell,我认为答案是每个循环的FLOPS用于沙桥和haswell SSE2/AVX/AVX2.我需要将循环展开10次.
对于Ivy Bridge,我认为它是8.这是我的逻辑.AVX添加的延迟为3,延迟乘以5.Ivy Bridge可以使用不同的端口同时进行一次AVX乘法和一次AVX添加.使用符号m进行乘法,a表示加法,x表示无操作,以及表示部分和的数字(例如m5表示乘以第5部分和)我可以写:
port0: m1 m2 m3 m4 m5 m6 m7 m8 m1 m2 m3 m4 m5 ...
port1: x x x x x a1 a2 a3 a4 a5 a6 a7 a8 ...
Run Code Online (Sandbox Code Playgroud)
因此,通过在9个时钟周期后使用8个部分和(4个来自负载,5个来自乘法),我可以在每个时钟周期提交一个AVX负载,一个AVX加法和一个AVX乘法.
我想这意味着在Ivy …
我一直在寻找相当长一段时间,似乎无法找到一个官方/结论性的数字引用英特尔至强四核可以完成的单精度浮点运算/时钟周期的数量.我有一个Intel Xeon quadcore E5530 CPU.
我希望用它来计算我的CPU可以达到的最大理论FLOP/s.
MAX FLOPS =(#内核数)*(时钟频率(周期/秒))*(#FLOPS /周期)
任何指向我正确方向的东西都会有用.我已经发现每个循环的这个 FLOPS用于沙桥和haswell SSE2/AVX/AVX2
英特尔酷睿2和Nehalem:
4 DP FLOP /周期:2宽SSE2加+ 2宽SSE2乘法
8 SP FLOP /周期:4宽SSE加法+ 4宽SSE乘法
但我不确定这些数据在哪里被发现.他们是假设融合乘法加法(FMAD)操作吗?
编辑:使用它,在DP中我计算出英特尔引用的正确DP算术吞吐量为38.4 GFLOP/s(此处引用).对于SP,我得到双倍,76.8 GFLOP/s.我很确定4 DP FLOP /周期和8 SP FLOP /周期是正确的,我只想确认他们如何获得4和8的FLOP /周期值.
Desktop i7-4770k @ 4GHz核心的峰值GFLOPS为4GHz*8(AVX)*(4 FMA)*4核= 512 GFLOPS.但最新的英特尔IGP(Iris Pro 5100/5200)峰值超过800 GFLOPS.因此,一些算法在IGP上运行得更快.将核心与IGP结合在一起甚至会更好.此外,IGP不断消耗更多硅.Iris Pro 5100现在占硅的30%以上.似乎很清楚英特尔台式机处理器的发展方向.
据我所知,除了OpenCL/OpenGL之外,程序员大多忽略了英特尔IGP.我很想知道如何在没有OpenCL的情况下为计算机(例如SGEMM)编程英特尔高清显卡硬件?
补充评论: 他们不支持Linux上的高清显卡和OpenCL.我发现beignet是开源尝试,至少为Ivy Bridge高清显卡增加了对Linux的支持.我没试过.可能是开发Beignet的人知道如何在没有OpenCL的情况下对HD图形硬件进行编程.
这个问题适用于Haswell上带有XMM/YMM寄存器的压缩单预备浮点运算.
因此,根据Agner Fog 提供的令人敬畏的,令人敬畏的 表,我知道MUL可以在端口p0和p1上完成(recp thruput为0.5),而只有ADD只在端口p1上完成(recp thruput为1) ).我可以除了这个限制,但我也知道FMA可以在端口p0或p1上完成(recp吞吐量为0.5).因此,当我的FMA可以使用p0或p1并同时执行ADD和MUL时,为什么普通ADD仅限于p1是令人困惑的.我误解了桌子吗?或者有人可以解释为什么会这样?
也就是说,如果我的读数是正确的,为什么英特尔不会仅使用FMA op作为普通MUL和普通ADD的基础,从而增加ADD和MUL的吞吐量.或者,什么会阻止我使用两个同时独立的FMA操作来模拟两个同时独立的ADD操作?做ADD-by-FMA有哪些处罚?显然,使用的寄存器数量更多(ADD为2 reg,而FMA为ADD为3 reg),但除此之外?
我想知道 Raspberry Pi 1 中的 ARM1176JZF-S 内核和 Raspberry Pi 2 中的 Cortex-A7 内核的每个周期的峰值 FLOP。
从ARM1176JZF-S 技术参考手册中可以看出,VFPv2 每个时钟周期可以执行一个 SP MAC,每隔一个时钟周期执行一个 DP MAC。此外,还有三个可以并行操作的管道:MAC 管道 (FMAC)、除法和开方管道 (DS) 以及加载/存储管道 (LS)。基于此,看来 Raspberry PI 1 的 ARM1176JZF-S 至少可以做到(来自 FMAC 管道)
维基百科声称树莓派 PI 1 的 FLOPS 为0.041 DP GFLOPS。除以 0.700 GHz 后,每周期的 DP FLOP 数少于 0.06。这比我估计的 1 DP FLOP/cycle 少了大约 17 倍。
那么正确答案是什么呢?
对于 Raspberry Pi 2 中的 Cortex-A7 处理器,我相信它与 Cortex-A9 …
我刚刚完成了基于 AMD Ryzen 2700x 和 32GB RAM(运行 Ubuntu 18.04)的台式计算机的安装。在工作中,我有一台使用了 3 年的笔记本电脑工作站,配备 Intel i7-6820HQ 和 16GB RAM(运行 Windows 10)。
我在两个平台上都安装了 Anaconda 并运行了一个自定义 Python 代码,该代码严重依赖于基本的 numpy 矩阵运算。代码不涉及任何 GPU 特定的计算(我的工作笔记本电脑没有)。Ryzen 运行在 3.7GHz,笔记本电脑 i7 运行在 3.6GHz。两个系统都已完全更新。
令我惊讶的是,代码在我的工作笔记本电脑上运行 5 分钟,而在锐龙台式机上需要 10 分钟!
最新的锐龙 2700x 应该比使用了 3 年的高端笔记本电脑英特尔处理器快得多,那为什么会慢 2 倍呢?
是因为 Ubuntu 在某些方面不是最佳的,而不是 Ryzen 的 Windows 10?
是因为英特尔比 AMD 更适合 Python 模拟吗?
还要别的吗?
感谢您帮助了解正在发生的事情。
灵感来自这个答案来
每个循环的FLOPS用于沙桥和haswell SSE2/AVX/AVX2
对于Sandy/Ivy Bridge,Broad/Haswell,Sky/Kaby Lake,可以在核心上发布的正常装载/装载和存储的数量是多少?同样有趣的是AMD Bulldozer,Jaguar和Zen的数量.
PS - 我知道由于缓存/内存带宽可能不是可持续的速率,我只是询问问题.
intel ×4
avx ×3
sse ×3
c ×2
c++ ×2
cpu ×2
flops ×2
fma ×2
performance ×2
x86 ×2
architecture ×1
arm ×1
assembly ×1
gpu ×1
linux ×1
low-level ×1
nehalem ×1
opencl ×1
optimization ×1
python ×1
raspberry-pi ×1
throughput ×1