问题描述
我有一个图像处理算法可以用 AVX 计算 function convertDataToQuarterly(dataSet) {
var quarterlyData = [];
var isCategoriesData = isNaN(dataSet[0]);
dataSet.forEach(function(dataEl,index) {
if ((index + 1) % 3 === 0) {
quarterlyData.push(dataEl);
if (!isCategoriesData) {
quarterlyData[quarterlyData.length - 1] += dataSet[index - 1] + dataSet[index - 2];
}
}
});
return quarterlyData;
}
。伪代码如下:
#!/usr/bin/env python
# -*- coding: UTF-8*-
import wx
import wx.adv
class TestFrame(wx.Frame):
def __init__(self,*args,**kwds):
kwds["style"] = kwds.get("style",0) | wx.DEFAULT_FRAME_STYLE
wx.Frame.__init__(self,**kwds)
self.btnShowDialog = wx.Button(self,wx.ID_ANY,"ShowDialog")
#Button bind:
self.btnShowDialog.Bind(wx.EVT_BUTTON,self.OnBtnShowDialog)
self.__set_properties()
self.__do_layout()
def __set_properties(self):
self.SetSize((400,300))
self.SetTitle("Test Frame")
def __do_layout(self):
sizer_1 = wx.GridSizer(1,1,0)
sizer_1.Add(self.btnShowDialog,wx.ALIGN_CENTER,0)
self.SetSizer(sizer_1)
self.Layout()
def OnBtnShowDialog(self,event):
# Shows my own dialog
dlg = LinkDialog(self,title="My Custom Dialog")
dlg.ShowModal()
# end of class TestFrame
class LinkDialog(wx.Dialog):
def __init__(self,**kwds):
kwds["style"] = kwds.get("style",0) | wx.DEFAULT_DIALOG_STYLE
wx.Dialog.__init__(self,**kwds)
# using wx.adv.HyperlinkCtrl
self.hyperlink_1 = wx.adv.HyperlinkCtrl(self,"Go to Stackoverflow","https://stackoverflow.com/")
self.__set_properties()
self.__do_layout()
def __set_properties(self):
self.SetSize((400,260))
self.hyperlink_1.SetFont(wx.Font(12,wx.FONTFAMILY_DEFAULT,wx.FONTSTYLE_norMAL,wx.FONTWEIGHT_norMAL,""))
def __do_layout(self):
grid_sizer_7 = wx.GridSizer(1,0)
grid_sizer_7.Add(self.hyperlink_1,0)
self.SetSizer(grid_sizer_7)
self.Layout()
# end of class LinkDialog
class MyApp(wx.App):
def OnInit(self):
self.frame = TestFrame(None,"")
self.SetTopWindow(self.frame)
self.frame.Show()
return True
# end of class MyApp
if __name__ == "__main__":
app = MyApp(0)
app.MainLoop()
我将上面的 _mm256_mul_ps 和 _mm256_add_ps 改为 _mm256_fmadd_ps 如下:
a*b+c*d
但是下面的代码比上面的要慢!上面代码执行时间1为50ms,下面代码执行时间2为90ms。 _mm256_fmadd_ps 比 _mm256_mul_ps + _mm256_add_ps 慢???
我使用 Ubuntu 16.04,GCC 7.5.0,编译器标志:-fopenmp -march=native -O3
解决方法
您的缩减循环既是延迟瓶颈,又不是吞吐量瓶颈,因为您只使用了一个 FP 向量累加器。 FMA 速度较慢,因为您使关键路径更长(每个循环迭代有 2 条指令链,而不是 1 条指令)。
在 add
的情况下,循环携带 sum
的依赖链只有 sum=_mm256_add_ps(sum,abcdm);
。其他指令对于每次迭代都是独立的,并且可以在前一个 abcdm
准备好此迭代的 vaddps
之前准备好 sum
输入。
在 fma
的情况下,循环携带的 dep 链经过两个 _mm256_fmadd_ps
操作,都进入 sum
,所以是的,您预计它会慢两倍.
使用更多累加器展开以隐藏 FP 延迟(就像点积的正常情况)。请参阅 Why does mulss take only 3 cycles on Haswell,different from Agner's instruction tables? (Unrolling FP loops with multiple accumulators) 以了解更多详情以及 OoO exec 的工作原理。
另请参阅 Improving performance of floating-point dot-product of an array with SIMD 以了解更简单的初学者友好的 2 个累加器示例。
(将那些单独的 __m256 sum0,sum1,sum2,etc
变量相加应该在循环之后完成。您还可以使用 __m256 sum[4]
来节省输入。您甚至可以在该数组上使用内部循环;大多数编译器将完全展开小的固定计数循环,因此您可以在单独的 YMM 寄存器中使用每个 __m256
获得所需的展开 asm。)
或者让 clang 自动矢量化;它通常会为您展开多个累加器。
或者,如果您出于某种原因不想展开,您可以使用 FMA,同时通过 sum += fma(a,b,c*d);
(一倍,一 FMA,一加)保持低循环携带延迟。当然,假设您的编译器没有“收缩”您的 mul 并为您添加到 FMA 中,如果您使用 -ffast-math
; 进行编译;默认情况下,GCC 会在语句之间积极地执行此操作,clang 不会。
一旦你这样做,你的吞吐量将在每个时钟 2 个负载上出现瓶颈(最好的情况是即使对齐的阵列没有缓存线分割,new
不会给你) ,所以使用 FMA 除了减少前端瓶颈几乎没有帮助。 (与每个加载需要运行 1 FP op 以跟上的多累加器 mul/add 版本相比;使用多个累加器将使您比原始循环更快。就像每 2 个周期进行一次迭代(4 次加载),而不是 1每 3 个周期,vaddps
延迟瓶颈)。
在 Skylake 及更高版本上,FMA/add/mul 都具有相同的延迟:4 个周期。在 Haswell/Broadwell 上,vaddps 延迟为 3 个周期(一个专用 FP 添加单元),而 FMA 延迟为 5。
Zen2 有 3 个周期 vaddps,5 个周期 vfma....ps (https://uops.info/)。 (两个/时钟吞吐量,以及在不同的执行端口上,因此理论上您可以在 Zen2 上每个时钟运行 2 个 FMA 和 2 vaddps。)
由于您的较长延迟 FMA 循环的速度慢了不到两倍,我猜您可能使用的是 Skylake 派生的 CPU。也许 mul/add 版本在前端或资源冲突或其他方面有点瓶颈,并没有完全实现每 3 个时钟 1 次迭代的预期延迟限制速度。
一般来说,请参阅 https://uops.info/ 以了解延迟和 uops/端口故障。 (也https://agner.org/optimize/)。