听起来您似乎想要一种方法来评估代码与FPU绑定的程度,或者您使用FPU的效率如何,而不是根据“触发器”的过时定义来计算触发器的数量。换句话说,如果每个浮点单元每个周期都以最大容量运行,则您希望指标达到相同的峰值。让我们看一看英特尔Sandy Bridge,看看这可能会如何解决。
硬件支持的浮点运算
该芯片支持AVX指令,因此寄存器的长度为32个字节(保持4个双精度)。超标量体系结构允许指令重叠,大多数算术指令需要几个周期才能完成,即使一条新指令可能能够在下一个周期开始。这些语义通常通过写等待时间/逆吞吐量来缩写,值5/2表示该指令需要5个周期才能完成,但是您可以每隔一个周期开始一条新指令(假设操作数可用,因此没有数据)依赖性,而不是等待内存)。
每个内核有三个浮点运算单元,但是第三个与我们的讨论无关,我们将相关的两个称为A和M单元,因为它们的主要功能是加法和乘法。示例说明(请参阅Agner Fog的表)
vaddpd
:打包加法,占用单元A 1个周期,延迟/逆吞吐量为3/1
vmulpd
:压缩乘法,单位M,5/1
vmaxpd
:打包选择成对最大值,单位A,3/1
vdivpd
:填充分隔,单位M(和一些A),取决于输入21/20至45/44
vsqrtpd
:压缩平方根,一些A和M,取决于输入21/21至43/43
vrsqrtps
:用于单精度输入的压缩低精度倒数平方根(8 floats
)
什么可以重叠的精确语义vdivpd
和vsqrtpd
显然是微妙的,据我所知,没有任何相关文档。在大多数使用中,我认为重叠的可能性很小,尽管手册中的措辞表明该指令中多个线程可能提供重叠的更多可能性。如果我们在每个周期开始执行vaddpd
,则可以达到峰值触发器vmulpd
,每个周期总共8次触发器。密集矩阵-矩阵乘法(dgemm
)可以合理地接近此峰值。
在对触发器进行特殊说明时,我会看一下FPU占用了多少。假设在您的输入范围内,vdivpd
平均需要24个周期才能完成,完全占用了单元M,但是(如果有)加法可以同时执行一半周期。FPU能够在这些周期(完全交错vaddpd
和vmulpd
)中执行24个压缩乘法和24个压缩加法,但是对于a vdivpd
,我们能做的最好的是12个额外的压缩加法。如果我们假设进行除法的最佳方法是使用硬件(合理的),则可以将vdivpd
“ 36个装箱的”触发器” 算为数字,这表示我们应将每个标量除法算作“ 36个触发器”。
使用平方根的倒数,有时可以击败硬件,尤其是在不需要完全精度或输入范围狭窄的情况下。如上所述,该vrsqrtps
指令非常便宜,因此(如果采用单精度)可以先执行一次,vrsqrtps
然后进行一到两次牛顿迭代来清理。这些牛顿迭代只是
y *= (3 - x*y*y)*0.5;
如果需要执行这些操作中的许多操作,则这可能会比单纯评估快得多y = 1/sqrt(x)
。在提供硬件近似的平方根倒数之前,一些对性能敏感的代码使用臭名昭著的整数运算来查找牛顿迭代的初始猜测。
图书馆提供的数学函数
我们可以将类似的启发式方法应用于库提供的数学函数。您可以通过分析来确定SSE指令的数量,但是,正如我们已经讨论的那样,这还不是全部内容,并且一个花费所有时间来评估特殊功能的程序可能看起来不会接近高峰,这可能是对的,但事实并非如此。告诉您所有时间都花在FPU的控制上,这没什么用。
我建议使用良好的矢量数学库作为基准(例如Intel的VML,属于MKL)。测量每个呼叫的周期数,然后乘以该周期数上可达到的峰值触发器。因此,如果压缩指数需要50个周期来评估,则将其计数为100触发器乘以寄存器宽度。不幸的是,矢量数学库有时很难调用并且没有所有特殊功能,因此您可能最终要进行标量数学,在这种情况下,您会将我们假设的标量指数算作100 flops(即使可能仍需要50 flops)周期,因此,如果所有时间都花在评估这些指数上,您将只获得25%的“峰值”。
正如其他人提到的那样,您可以使用PAPI或各种接口来计数周期和硬件事件计数器。对于简单的周期计数,您可以使用rdtsc
带有内联汇编代码段的指令直接读取周期计数器。