X86 使用AVX内部函数的外接圆参数

X86 使用AVX内部函数的外接圆参数,x86,clang,c++14,avx,voronoi,X86,Clang,C++14,Avx,Voronoi,使用带有透明比较器的本机STL树在平面上实现生成。目前发现的几乎所有bug和实现的渐进复杂性都是O(N*log(N))。现在我想尝试做一些优化。通过分析结果(我使用pproffrom),我得出结论,有两个非常热门的函数。它们都表达了非常惯用的数学算法:计算给定公共准线的两条抛物线交点的纵坐标,以及由3个点计算外接圆的圆心和半径。第二个是有趣的。3个点的外接圆(根据Voronoi图的位置)表示Voronoi图顶点,根据Fortune算法称为“圆事件”。顶点查找的函数(基于)如下所示: 使用valu

使用带有透明比较器的本机STL树在平面上实现生成。目前发现的几乎所有bug和实现的渐进复杂性都是
O(N*log(N))
。现在我想尝试做一些优化。通过分析结果(我使用
pprof
from),我得出结论,有两个非常热门的函数。它们都表达了非常惯用的数学算法:计算给定公共准线的两条抛物线交点的纵坐标,以及由3个点计算外接圆的圆心和半径。第二个是有趣的。3个点的外接圆(根据Voronoi图的位置)表示Voronoi图顶点,根据Fortune算法称为“圆事件”。顶点查找的函数(基于)如下所示:

使用value\u type=double;
结构路线(uuu m128d)点
{
值_类型x,y;
};
标准::实验::可选<顶点>
生成顶点(点常量和a,
康斯特角酒店,
点常数(c)
{
#如果1
值_类型常数A=A.x*A.x+A.y*A.y;
值_类型常数B=B.x*B.x+B.y*B.y;
值_type const C=C.x*C.x+C.y*C.y;
点常数ca={a.x-c.x,a.y-c.y};
点常数cb={b.x-c.x,b.y-c.y};
值\类型常数CA=A-C;
值\类型常数CB=B-C;
值_type x=CA*cb.y-cb*CA.y;
值_type y=ca.x*CB-CB.x*ca;
值_type alpha=ca.x*cb.y-ca.y*cb.x;
如果(!(每股收益<-α)){
返回{};
}
β值=a.x*(b.y*C-C.y*b)-b.x*(a.y*C-C.y*a)+C.x*(a.y*b-b.y*a);
β/=α;
α+=α;
x/=α;
y/=α;
使用std::sqrt;
值_type const R=sqrt(β+x*x+y*y);
返回{{x,y},R};
#否则
__m256d a=mm256广播pd(((m128d*)和a);
__m256d b=mm256广播pd(((m128d*)和b);
__m256d c=_mm256_广播_pd((u m128d*)和c);
__m256d A=_mm256_mul_pd(A,A);
A=_mm256_hadd_pd(A,A);
__m256d B=_mm256_mul_pd(B_,B_);
B=_mm256_hadd_pd(B,B);
__m256d C=_mm256_mul_pd(C,C);
C=_mm256_hadd_pd(C,C);
__m256d byayaxbx=_mm256_permute_pd(_mm256_shuffle_pd(_mm256_sub_pd(a_,c_),_mm256_sub_pd(b_,c_),0b0011),0b1001);
__m256d ABBA=_mm256_permute_pd(_mm256_sub_pd(_mm256_shuffle_pd(A,B,0),C),0b0110);
__m256d xxyy=_mm256_mul_pd(byayaxbx,ABBA);
xxyy=_mm256_hsub_pd(xxyy,xxyy);
__m256d xyxy=_mm256_shuffle_pd(xxyy,_mm256_permute2f128_pd(xxyy,xxyy,0x01),0);
__m256d alpha=_mm256_mul_pd(byayaxbx,_mm256_permute2f128_pd(byayaxbx,byayaxbx,0x01));
alpha=_mm256_hsub_pd(alpha,alpha);
如果(!(α[0]<-eps)){
返回{};
}
__m256d tmp1=_mm256_mul_pd(_mm256_permute_pd(a,0b1001),_mm256_permute2f128_pd(c_,_mm256_permute_pd(b_,0b01),0x20));
__m256d tmp2=_mm256_mul_pd(b_,_mm256_permute_pd(c_,0b01));
__m256d bacc=_mm256_permute_pd(_mm256_hsub_pd(_mm256_mul_pd(tmp1,_mm256_permute2f128_pd(B,C,0x20)),_mm256_mul_pd(tmp2,A)),0b0010;
bacc=_mm256_div_pd(bacc,alpha);
xyxy=_mm256_div_pd(xyxy,_mm256_add_pd(alpha,alpha));
__m256d测试版=_mm256_hadd_pd(bacc,_mm256_mul_pd(xyxy,XYXYXY));
贝塔=_mm256_hadd_pd(贝塔,贝塔);
beta=_mm256_sqrt_pd(_mm256_add_pd(_mm256_permute2f128_pd(bacc,bacc,0x01),beta));
返回{{xyxy[0],xyxy[1]},beta[0]};
#恩迪夫
}
正如你所看到的,我试图用intrisic来重新实现这个函数。
#if 1
#if 0
两个部分完全等效且正确<代码>#如果0大部分时间使用ymm寄存器的全宽。只有在最后,某些变量的上半部分才包含垃圾

我对一个巨大的输入进行了两次测试(100000个点均匀分布在飞机的有限部分上):一个用于
#if 1
,另一个用于
#if 0
。第一种方法提供了更好的运行时间(0.28秒,而多次尝试的平均时间为0.31秒)。我查看了
#if 1的反汇编:

push   %rbx
sub    $0x10,%rsp
mov    %rdi,%rbx
        319 [1]            value_type const A = a.x * a.x + a.y * a.y;
vmovsd (%rdx),%xmm11
vmovsd 0x8(%rdx),%xmm0
        320 [1]            value_type const B = b.x * b.x + b.y * b.y;
vmovsd (%rcx),%xmm9
vmovsd 0x8(%rcx),%xmm2
        321 [1]            value_type const C = c.x * c.x + c.y * c.y;
vmovsd (%r8),%xmm8
vmovsd 0x8(%r8),%xmm6
        322 [1]            point const ca = {a.x - c.x, a.y - c.y};
vunpcklpd %xmm9,%xmm0,%xmm1
vunpcklpd %xmm8,%xmm6,%xmm5
vsubpd %xmm5,%xmm1,%xmm7
        323 [1]            point const cb = {b.x - c.x, b.y - c.y};
vunpcklpd %xmm11,%xmm2,%xmm1
vsubpd %xmm5,%xmm1,%xmm1
        328 [1]            value_type alpha = ca.x * cb.y - ca.y * cb.x;
vpermilpd $0x1,%xmm1,%xmm5
vmulsd %xmm1,%xmm5,%xmm5
vpermilpd $0x1,%xmm7,%xmm3
vmulsd %xmm7,%xmm3,%xmm3
vsubsd %xmm3,%xmm5,%xmm12
        329 [1]            if (!(eps < -alpha)) {
mov    (%rsi),%rax
vxorpd 0x1cd1(%rip),%xmm12,%xmm3        # 0x40e680
vucomisd (%rax),%xmm3
jbe    0x40ca85 <make_vertex()+309>
        319 [2]            value_type const A = a.x * a.x + a.y * a.y;
vunpcklpd %xmm9,%xmm11,%xmm3
vmulpd %xmm3,%xmm3,%xmm10
vunpcklpd %xmm2,%xmm0,%xmm3
vmulpd %xmm3,%xmm3,%xmm3
vaddpd %xmm3,%xmm10,%xmm3
        321 [2]            value_type const C = c.x * c.x + c.y * c.y;
vmulsd %xmm8,%xmm8,%xmm10
vmulsd %xmm6,%xmm6,%xmm4
vaddsd %xmm4,%xmm10,%xmm4
        324 [1]            value_type const CA = A - C;
vmovddup %xmm4,%xmm5
vsubpd %xmm5,%xmm3,%xmm5
        326 [1]            value_type x = CA * cb.y - CB * ca.y;
vmulpd %xmm5,%xmm1,%xmm1
vpermilpd $0x1,%xmm5,%xmm5
vmulpd %xmm5,%xmm7,%xmm5
vsubpd %xmm5,%xmm1,%xmm10
        332 [1]            value_type beta = a.x * (b.y * C - c.y * B) + b.x * (c.y * A - a.y * C) + c.x * (a.y * B - b.y * A);
vmulsd %xmm4,%xmm2,%xmm5
vpermilpd $0x1,%xmm3,%xmm7
vmulsd %xmm7,%xmm6,%xmm1
vsubsd %xmm1,%xmm5,%xmm1
vmulsd %xmm1,%xmm11,%xmm1
vmulsd %xmm6,%xmm3,%xmm5
vmulsd %xmm4,%xmm0,%xmm4
vsubsd %xmm4,%xmm5,%xmm4
vmulsd %xmm4,%xmm9,%xmm4
vaddsd %xmm4,%xmm1,%xmm1
vmulsd %xmm7,%xmm0,%xmm0
vmulsd %xmm3,%xmm2,%xmm2
vsubsd %xmm2,%xmm0,%xmm0
vmulsd %xmm0,%xmm8,%xmm0
vaddsd %xmm1,%xmm0,%xmm0
        333 [1]            beta /= alpha;
vdivsd %xmm12,%xmm0,%xmm0
        334 [1]            alpha += alpha;
vaddsd %xmm12,%xmm12,%xmm1
        335 [1]            x /= alpha;
vmovddup %xmm1,%xmm1
vdivpd %xmm1,%xmm10,%xmm2
        338 [1]            value_type const R = sqrt(beta + x * x + y * y);
vmulsd %xmm2,%xmm2,%xmm1
vaddsd %xmm0,%xmm1,%xmm0
vpermilpd $0x1,%xmm2,%xmm1
vmulsd %xmm1,%xmm1,%xmm1
vaddsd %xmm0,%xmm1,%xmm1
vsqrtsd %xmm1,%xmm1,%xmm0
        370 [1]        }
%rbx,%rax
$0x10,%rsp
%rbx
retq
我在
英特尔(R)核心(TM)i7-2670QM CPU上使用了
clang版本4.0.0(trunk 282862)
,关键的命令行键:
-O3-march=native-mavx

我在这里的主要误解是什么


为了更好的控制,我应该使用内联汇编吗?

恕我直言,但我不相信你。这看起来很像调试代码,溢出到堆栈并立即重新加载在优化代码中并不是什么事情,但它总是发生在这里。我继续自己编译这个只是为了确认,我用-O3得到了完全合理的代码。我承认,不是用叮当声4,我没有。“如果他们真的有这么大的回归,我会感到惊讶,这基本上会使它无法使用。”哈罗德我制作和编辑。我在
RelWithDebInfo
模式下编译了这两个版本。现在,这两种情况下的代码都与我预期的一样。但我错了什么?为什么性能不同?行表似乎与代码不太一致。当然这是因为代码经过了高度优化(
-O3
)。行号有时会做一些奇怪的事情,可能没什么大不了的。不管怎样,时间还是对的?这里有很多不是很快的指令,比如水平操作和跨通道的指令,比如
vinsertf128
vperm2f128
,也许你可以使用更少的指令。它们不是超慢的,但是对于混洗类型的指令,它们很慢,而且是开销——这不是计算结果。因此,您需要一些除法和平方根,因为它基本上是您计算的一部分(也有近似值,但您使用double表示这不好),但也许您可以保存一些数据移动,特别是较慢的数据移动。
        317 [1]        {
vmovupd (%rdx),%xmm2
vmovupd (%rcx),%xmm1
vmovupd (%r8),%xmm4
        350 [1]            __m256d byayaxbx = _mm256_permute_pd(_mm256_shuffle_pd(_mm256_sub_pd(a_, c_), _mm256_sub_pd(b_, c_), 0b0011), 0b1001);
vsubpd %xmm4,%xmm2,%xmm0
vinsertf128 $0x1,%xmm0,%ymm0,%ymm0
vsubpd %xmm4,%xmm1,%xmm3
vinsertf128 $0x1,%xmm3,%ymm3,%ymm3
vpermilpd $0x1,%ymm3,%ymm3
vblendpd $0x6,%ymm0,%ymm3,%ymm5
        355 [1]            __m256d alpha = _mm256_mul_pd(byayaxbx, _mm256_permute2f128_pd(byayaxbx, byayaxbx, 0x01));
vperm2f128 $0x1,%ymm0,%ymm5,%ymm0
vmulpd %ymm0,%ymm5,%ymm0
        356 [1]            alpha = _mm256_hsub_pd(alpha, alpha);
vhsubpd %ymm0,%ymm0,%ymm0
        357 [1]            if (!(alpha[0] < -eps)) {
mov    (%rsi),%rax
vmovsd (%rax),%xmm3
vxorpd 0x1cc6(%rip),%xmm3,%xmm3        # 0x40e660
vucomisd %xmm0,%xmm3
jbe    0x40ca6d <make_vertex()+285>
        343 [1]            __m256d c_ = _mm256_broadcast_pd((__m128d *)&c);
vinsertf128 $0x1,%xmm4,%ymm4,%ymm6
        344 [1]            __m256d A = _mm256_mul_pd(a_, a_);
vmulpd %xmm2,%xmm2,%xmm3
vinsertf128 $0x1,%xmm3,%ymm3,%ymm3
        345 [1]            A = _mm256_hadd_pd(A, A);
vhaddpd %ymm3,%ymm3,%ymm3
        346 [1]            __m256d B = _mm256_mul_pd(b_, b_);
vmulpd %xmm1,%xmm1,%xmm7
vinsertf128 $0x1,%xmm7,%ymm7,%ymm7
        347 [1]            B = _mm256_hadd_pd(B, B);
vhaddpd %ymm7,%ymm7,%ymm7
        348 [1]            __m256d C = _mm256_mul_pd(c_, c_);
vmulpd %xmm4,%xmm4,%xmm4
vinsertf128 $0x1,%xmm4,%ymm4,%ymm4
        349 [1]            C = _mm256_hadd_pd(C, C);
vhaddpd %ymm4,%ymm4,%ymm4
        351 [1]            __m256d ABBA = _mm256_permute_pd(_mm256_sub_pd(_mm256_shuffle_pd(A, B, 0), C), 0b0110);
vunpcklpd %ymm7,%ymm3,%ymm8
vsubpd %ymm4,%ymm8,%ymm8
vpermilpd $0x6,%ymm8,%ymm8
        352 [1]            __m256d xxyy = _mm256_mul_pd(byayaxbx, ABBA);
vmulpd %ymm8,%ymm5,%ymm5
        353 [1]            xxyy = _mm256_hsub_pd(xxyy, xxyy);
vhsubpd %ymm5,%ymm5,%ymm5
        342 [1]            __m256d b_ = _mm256_broadcast_pd((__m128d *)&b);
vinsertf128 $0x1,%xmm1,%ymm1,%ymm8
        341 [1]            __m256d a_ = _mm256_broadcast_pd((__m128d *)&a);
vinsertf128 $0x1,%xmm2,%ymm2,%ymm2
        354 [1]            __m256d xyxy = _mm256_shuffle_pd(xxyy, _mm256_permute2f128_pd(xxyy, xxyy, 0x01), 0);
vperm2f128 $0x23,%ymm5,%ymm0,%ymm9
vunpcklpd %ymm9,%ymm5,%ymm5
        360 [1]            __m256d tmp1 = _mm256_mul_pd(_mm256_permute_pd(a_, 0b1001), _mm256_permute2f128_pd(c_, _mm256_permute_pd(b_, 0b01), 0x20));
vpermilpd $0x9,%ymm2,%ymm2
vpermilpd $0x1,%xmm1,%xmm1
vinsertf128 $0x1,%xmm1,%ymm6,%ymm1
vmulpd %ymm1,%ymm2,%ymm1
        361 [1]            __m256d tmp2 = _mm256_mul_pd(b_, _mm256_permute_pd(c_, 0b01));
vpermilpd $0x1,%ymm6,%ymm2
vmulpd %ymm2,%ymm8,%ymm2
        362 [1]            __m256d bacc = _mm256_permute_pd(_mm256_hsub_pd(_mm256_mul_pd(tmp1, _mm256_permute2f128_pd(B, C, 0x20)), _mm256_mul_pd(tmp2, A)), 0b0010);
vinsertf128 $0x1,%xmm4,%ymm7,%ymm4
vmulpd %ymm4,%ymm1,%ymm1
vmulpd %ymm2,%ymm3,%ymm2
vhsubpd %ymm2,%ymm1,%ymm1
vpermilpd $0x2,%ymm1,%ymm1
        363 [1]            bacc = _mm256_div_pd(bacc, alpha);
vdivpd %ymm0,%ymm1,%ymm1
        364 [1]            xyxy = _mm256_div_pd(xyxy, _mm256_add_pd(alpha, alpha));
vaddpd %ymm0,%ymm0,%ymm0
vdivpd %ymm0,%ymm5,%ymm0
        365 [1]            __m256d beta = _mm256_hadd_pd(bacc, _mm256_mul_pd(xyxy, xyxy));
vmulpd %ymm0,%ymm0,%ymm2
vhaddpd %ymm2,%ymm1,%ymm2
        366 [1]            beta = _mm256_hadd_pd(beta, beta);
vhaddpd %ymm2,%ymm2,%ymm2
        367 [1]            beta = _mm256_sqrt_pd(_mm256_add_pd(_mm256_permute2f128_pd(bacc, bacc, 0x01), beta));
vperm2f128 $0x1,%ymm0,%ymm1,%ymm1
vaddpd %ymm1,%ymm2,%ymm1
vsqrtpd %ymm1,%ymm1
        370 [1]        }
mov    %rdi,%rax
vzeroupper
retq