- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我有一个用 AVX 计算 a*b+c*d
的图像处理算法。伪代码如下:
float *a=new float[N];
float *b=new float[N];
float *c=new float[N];
float *d=new float[N];
//assign values to a, b, c and d
__m256 sum;
double start=cv::getTickCount();
for (int i = 0; i < n; i += 8) // assume that n is a multiple of 8
{
__m256 am=_mm256_loadu_ps(a+i);
__m256 bm=_mm256_loadu_ps(b+i);
__m256 cm=_mm256_loadu_ps(c+i);
__m256 dm=_mm256_loadu_ps(d+i);
__m256 abm=_mm256_mul_ps(am, bm);
__m256 cdm=_mm256_mul_ps(cm, dm);
__m256 abcdm=_mm256_add_ps(abm, cdm);
sum=_mm256_add_ps(sum, abcdm);
}
double time1=(cv::getTickCount()-start)/cv::getTickFrequency();
我把上面的_mm256_mul_ps和_mm256_add_ps改为_mm256_fmadd_ps如下:
float *a=new float[N];
float *b=new float[N];
float *c=new float[N];
float *d=new float[N];
//assign values to a, b, c and d
__m256 sum;
double start=cv::getTickCount();
for (int i = 0; i < n; i += 8) // assume that n is a multiple of 8
{
__m256 am=_mm256_loadu_ps(a+i);
__m256 bm=_mm256_loadu_ps(b+i);
__m256 cm=_mm256_loadu_ps(c+i);
__m256 dm=_mm256_loadu_ps(d+i);
sum=_mm256_fmadd_ps(am, bm, sum);
sum=_mm256_fmadd_ps(cm, dm, sum);
}
double time2=(cv::getTickCount()-start)/cv::getTickFrequency();
但是下面的代码比上面的慢!上面代码执行时间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);
。其他指令对于每次迭代都是独立的,并且可以在前一个 vaddps
准备好本次迭代的 sum
之前准备好 abcdm
输入。
在 fma
的情况下,循环携带的 dep 链经过两个 _mm256_fmadd_ps
操作,都进入 sum
,所以是的,你d 预计它会慢两倍左右。
展开更多累加器以隐藏 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 等
变量相加应该在循环之后完成。您还可以使用 __m256 sum[4]
来节省输入。您甚至可以对该数组使用内部循环;大多数编译器将完全展开小的固定计数循环,因此您可以在单独的 YMM 寄存器中使用每个 __m256
获得所需的展开 asm。)
或者让 clang 自动向量化它;它通常会为您展开多个累加器。
或者,如果您出于某种原因不想展开,您可以使用 FMA,同时通过 sum += fma(a, b, c*d);
保持低循环延迟(一个 mul,一个 FMA,一个 add)。当然,如果您使用 -ffast-math
编译,假设您的编译器没有“收缩”您的 mul 并为您添加到 FMA 中;默认情况下,GCC 会积极地跨语句执行此操作,而 clang 不会。
一旦你这样做,你的吞吐量将在每个时钟 2 个负载上成为瓶颈(最好的情况是对齐阵列没有缓存行拆分,new
不会给你),所以除了减少前端瓶颈外,使用 FMA 几乎没有帮助。 (与需要每次加载运行 1 个 FP 运算才能跟上的多累加器 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/ )。 (两者的 2/clock 吞吐量,以及在不同的执行端口上,因此理论上您可以在 Zen2 上每个时钟运行 2 个 FMA 和 2 个 vaddps。)
由于您的较长延迟 FMA 循环的速度不到原来的两倍,我猜您可能使用的是 Skylake 派生的 CPU。也许 mul/add 版本在前端或资源冲突或其他方面存在一些瓶颈,并且没有完全达到预期的每 3 个时钟 1 次迭代延迟限制速度。
一般情况下,参见 https://uops.info/用于延迟和 uops/端口故障。(还有 https://agner.org/optimize/ )。
关于gcc - _mm256_fmadd_ps 比 _mm256_mul_ps + _mm256_add_ps 慢?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66260651/
我是一名优秀的程序员,十分优秀!