Assembly 有符号或无符号循环计数器

Assembly 有符号或无符号循环计数器,assembly,optimization,compiler-optimization,icc,unsigned-integer,Assembly,Optimization,Compiler Optimization,Icc,Unsigned Integer,在这个简单的示例中,使用有符号和无符号循环计数器之间的差异让我非常惊讶: double const* a; __assume_aligned(a, 64); double s = 0.0; //for ( unsigned int i = 0; i < 1024*1024; i++ ) for ( int i = 0; i < 1024*1024; i++ ) { s += a[i]; } 在无符号情况下,icc使用额外的寄存器来寻址内存,相应的LEAs: ..B1.2:

在这个简单的示例中,使用有符号和无符号循环计数器之间的差异让我非常惊讶:

double const* a;
__assume_aligned(a, 64);
double s = 0.0;

//for ( unsigned int i = 0; i < 1024*1024; i++ )
for ( int i = 0; i < 1024*1024; i++ )
{
    s += a[i];
}
在无符号情况下,icc使用额外的寄存器来寻址内存,相应的
LEA
s:

..B1.2:
    lea       edx, DWORD PTR [8+rax]
    vaddpd    zmm6, zmm6, ZMMWORD PTR [rdi+rdx*8]
    lea       ecx, DWORD PTR [16+rax]
    vaddpd    zmm5, zmm5, ZMMWORD PTR [rdi+rcx*8]
    vaddpd    zmm7, zmm7, ZMMWORD PTR [rdi+rax*8]
    lea       esi, DWORD PTR [24+rax]
    vaddpd    zmm4, zmm4, ZMMWORD PTR [rdi+rsi*8]
    lea       r8d, DWORD PTR [32+rax]
    vaddpd    zmm3, zmm3, ZMMWORD PTR [rdi+r8*8]
    lea       r9d, DWORD PTR [40+rax]
    vaddpd    zmm2, zmm2, ZMMWORD PTR [rdi+r9*8]
    lea       r10d, DWORD PTR [48+rax]
    vaddpd    zmm1, zmm1, ZMMWORD PTR [rdi+r10*8]
    lea       r11d, DWORD PTR [56+rax]
    add       eax, 64
    vaddpd    zmm0, zmm0, ZMMWORD PTR [rdi+r11*8]
    cmp       eax, 1048576
    jb        ..B1.2        # Prob 99%
对我来说,令人惊讶的是,它没有生成相同的代码(给定编译时循环计数)。这是一个编译器优化问题吗

编译选项:
-O3-march=skylake-avx512-mtune=skylake-avx512-qopt-zmm使用率=高
这是ICC愚蠢的遗漏优化。它不是特定于AVX512的;默认/常规拱门设置仍会发生这种情况

lea ecx,DWORD PTR[16+rax]
正在计算
i+16
作为展开的一部分,截断为32位(32位操作数大小),零扩展为64位(写入32位寄存器时在x86-64中隐式)。这在类型宽度处显式实现了无符号环绕的语义

gcc和clang在证明
无符号i
不会换行方面没有问题,因此他们可以优化零扩展,从32位无符号到64位指针宽度,以便在寻址模式中使用,因为循环上限是已知的1

记得在C和C++中,未签名的环绕是很好定义的,但是签名溢出是未定义的行为。这意味着有符号变量可以提升为指针宽度,并且编译器不必每次将它们用作数组索引时都将符号扩展重做为指针宽度。(

a[i]
相当于
*(a+i)
,向指针添加整数的规则意味着,对于寄存器高位可能不匹配的窄值,符号扩展是必要的。)

签名溢出UB是ICC能够正确优化签名计数器的原因,即使它无法使用范围信息。另请参见(关于未定义的行为)。请注意,它使用的是64位操作数大小的
addrax、64
cmp
(rax而不是EAX)


我将您的代码制作成MCVE,以便与其他编译器进行测试
\uuuuu-assemption\uu-aligned
仅为ICC,因此我使用了GNU C
\uuuuuuuuu-builtin\uu-assemption\uu-aligned

#define COUNTER_TYPE unsigned

double sum(const double *a) {
    a = __builtin_assume_aligned(a, 64);
    double s = 0.0;

    for ( COUNTER_TYPE i = 0; i < 1024*1024; i++ )
        s += a[i];
    return s;
}
我没有启用AVX,这不会改变循环结构。请注意,clang只使用2个向量累加器,所以如果L1d缓存中的数据是热的,它将在最新的CPU上增加延迟。Skylake一次最多可保持8个
addpd
(2个时钟吞吐量,4个周期延迟)。因此,对于二级缓存(特别是L1d缓存)中的(部分)数据很热的情况,ICC的表现要好得多

奇怪的是,如果clang打算添加/cmp,那么它没有使用指针增量。它只需要在循环之前执行两条额外的指令,并且可以简化寻址模式,即使在Sandybridge上也可以实现负载的微融合。(但它不是AVX,因此Haswell和更高版本可以保持负载微熔合。)。GCC做到了这一点,但根本不展开,这是GCC在没有配置文件引导优化的情况下的默认设置

无论如何,ICC的AVX512代码将在发布/重命名阶段(或者在添加到IDQ之前,我不确定)将分层为单独的加载并添加UOP。因此,它不使用指针增量来节省前端带宽、为更大的无序窗口消耗更少的ROB空间以及更友好的阅读,这是非常愚蠢的


脚注1:


(即使不是,没有副作用的无限循环(如
volatile
原子访问)是未定义的行为,因此即使使用
i,编译器似乎也无法推断截断到32位实际上是不必要的。它如何与
size\u t
一起工作?这是一个遗漏的优化,因为它可以证明那
i
不会换行,但会失败。但是签名溢出是UB,它可以将32位循环计数器提升到64位,而不必像每次在这里重复零扩展一样,每次都重复符号扩展。不是每次,而是每8次。@zch你是对的,
size\u t
生成与签名int相同的代码
#define COUNTER_TYPE unsigned

double sum(const double *a) {
    a = __builtin_assume_aligned(a, 64);
    double s = 0.0;

    for ( COUNTER_TYPE i = 0; i < 1024*1024; i++ )
        s += a[i];
    return s;
}
# clang 7.0 -O3
sum:                                    # @sum
    xorpd   xmm0, xmm0
    xor     eax, eax
    xorpd   xmm1, xmm1
.LBB0_1:                                # =>This Inner Loop Header: Depth=1
    addpd   xmm0, xmmword ptr [rdi + 8*rax]
    addpd   xmm1, xmmword ptr [rdi + 8*rax + 16]
    addpd   xmm0, xmmword ptr [rdi + 8*rax + 32]
    addpd   xmm1, xmmword ptr [rdi + 8*rax + 48]
    addpd   xmm0, xmmword ptr [rdi + 8*rax + 64]
    addpd   xmm1, xmmword ptr [rdi + 8*rax + 80]
    addpd   xmm0, xmmword ptr [rdi + 8*rax + 96]
    addpd   xmm1, xmmword ptr [rdi + 8*rax + 112]
    add     rax, 16                                  # 64-bit loop counter
    cmp     rax, 1048576
    jne     .LBB0_1

    addpd   xmm1, xmm0
    movapd  xmm0, xmm1         # horizontal sum
    movhlps xmm0, xmm1              # xmm0 = xmm1[1],xmm0[1]
    addpd   xmm0, xmm1
    ret