Pourquoi une boucle simple est-elle optimisée lorsque la limite est de 959 mais pas de 960?c


Answers

Après avoir lu le commentaire de Sulthan, je suppose que:

  1. Le compilateur déroule entièrement la boucle si le compteur de boucle est constant (et pas trop élevé)

  2. Une fois qu'il est déroulé, le compilateur voit que les opérations de somme peuvent être regroupées en une seule.

Si la boucle n'est pas déroulée pour une raison quelconque (ici: elle générerait trop d'instructions avec 1000 ), les opérations ne peuvent pas être groupées.

Le compilateur a pu voir que le déroulement de 1000 instructions équivaut à une seule addition, mais les étapes 1 et 2 décrites ci-dessus sont deux optimisations distinctes, donc il ne peut pas prendre le "risque" de dérouler, ne sachant pas si les opérations peuvent être groupées. un appel de fonction ne peut pas être groupé).

Note: C'est un cas de coin: Qui utilise une boucle pour rajouter la même chose? Dans ce cas, ne comptez pas sur le compilateur possible dérouler / optimiser; écrire directement le bon fonctionnement dans une instruction.

Question

Considérez cette simple boucle:

float f(float x[]) {
  float p = 1.0;
  for (int i = 0; i < 959; i++)
    p += 1;
  return p;
}

Si vous compilez avec gcc 7 (snapshot) ou clang (trunk) avec -march=core-avx2 -Ofast vous obtenez quelque chose de très similaire à.

.LCPI0_0:
        .long   1148190720              # float 960
f:                                      # @f
        vmovss  xmm0, dword ptr [rip + .LCPI0_0] # xmm0 = mem[0],zero,zero,zero
        ret

En d'autres termes, il définit simplement la réponse à 960 sans boucle.

Cependant, si vous changez le code pour:

float f(float x[]) {
  float p = 1.0;
  for (int i = 0; i < 960; i++)
    p += 1;
  return p;
}

L'assemblage produit effectue réellement la somme de boucle? Par exemple, clang donne:

.LCPI0_0:
        .long   1065353216              # float 1
.LCPI0_1:
        .long   1086324736              # float 6
f:                                      # @f
        vmovss  xmm0, dword ptr [rip + .LCPI0_0] # xmm0 = mem[0],zero,zero,zero
        vxorps  ymm1, ymm1, ymm1
        mov     eax, 960
        vbroadcastss    ymm2, dword ptr [rip + .LCPI0_1]
        vxorps  ymm3, ymm3, ymm3
        vxorps  ymm4, ymm4, ymm4
.LBB0_1:                                # =>This Inner Loop Header: Depth=1
        vaddps  ymm0, ymm0, ymm2
        vaddps  ymm1, ymm1, ymm2
        vaddps  ymm3, ymm3, ymm2
        vaddps  ymm4, ymm4, ymm2
        add     eax, -192
        jne     .LBB0_1
        vaddps  ymm0, ymm1, ymm0
        vaddps  ymm0, ymm3, ymm0
        vaddps  ymm0, ymm4, ymm0
        vextractf128    xmm1, ymm0, 1
        vaddps  ymm0, ymm0, ymm1
        vpermilpd       xmm1, xmm0, 1   # xmm1 = xmm0[1,0]
        vaddps  ymm0, ymm0, ymm1
        vhaddps ymm0, ymm0, ymm0
        vzeroupper
        ret

Pourquoi est-ce et pourquoi est-ce exactement la même chose pour clang et gcc?

La limite pour la même boucle si vous remplacez float avec double est 479. C'est la même chose pour gcc et clang encore.

Mise à jour 1

Il s'avère que gcc 7 (snapshot) et clang (trunk) se comportent très différemment. clang optimise les boucles pour toutes les limites inférieures à 960 pour autant que je sache. D'autre part, gcc est sensible à la valeur exacte et n'a pas de limite supérieure. Par exemple, il n'optimise pas la boucle lorsque la limite est de 200 (ainsi que de nombreuses autres valeurs) mais elle le fait lorsque la limite est de 202 et 20002 (ainsi que de nombreuses autres valeurs).