3 votes

Problèmes d'assemblage généré par le compilateur pour les intrinsèques

J'utilise les intrinsèques SSE/AVX/FMA d'Intel pour réaliser un inlining parfait des instructions SSE/AVX pour certaines fonctions mathématiques.

Étant donné le code suivant

#include <cmath>
#include <immintrin.h>

auto std_fma(float x, float y, float z)
{
    return std::fma(x, y, z);
}

float _fma(float x, float y, float z)
{
    _mm_store_ss(&x,
        _mm_fmadd_ss(_mm_load_ss(&x), _mm_load_ss(&y), _mm_load_ss(&z))
    );

    return x;
}

float _sqrt(float x)
{
    _mm_store_ss(&x,
        _mm_sqrt_ss(_mm_load_ss(&x))
    );

    return x;
}

l'assemblage généré par clang 3.9 avec -march=x86-64 -mfma -O3

std_fma(float, float, float):                          # @std_fma(float, float, float)
        vfmadd213ss     xmm0, xmm1, xmm2
        ret

_fma(float, float, float):                             # @_fma(float, float, float)
        vxorps  xmm3, xmm3, xmm3
        vmovss  xmm0, xmm3, xmm0        # xmm0 = xmm0[0],xmm3[1,2,3]
        vmovss  xmm1, xmm3, xmm1        # xmm1 = xmm1[0],xmm3[1,2,3]
        vmovss  xmm2, xmm3, xmm2        # xmm2 = xmm2[0],xmm3[1,2,3]
        vfmadd213ss     xmm0, xmm1, xmm2
        ret

_sqrt(float):                              # @_sqrt(float)
        vsqrtss xmm0, xmm0, xmm0
        ret

tandis que le code généré pour _sqrt est bien, il y a des inutiles vxorps (qui met à zéro le registre xmm3 absolument inutilisé) et movss instructions dans _fma par rapport à std_fma (qui reposent sur le compilateur intrinsèque std::fma)

l'assemblage généré par GCC 6.2 avec -march=x86-64 -mfma -O3

std_fma(float, float, float):
        vfmadd132ss     xmm0, xmm2, xmm1
        ret
_fma(float, float, float):
        vinsertps       xmm1, xmm1, xmm1, 0xe
        vinsertps       xmm2, xmm2, xmm2, 0xe
        vinsertps       xmm0, xmm0, xmm0, 0xe
        vfmadd132ss     xmm0, xmm2, xmm1
        ret
_sqrt(float):
        vinsertps       xmm0, xmm0, xmm0, 0xe
        vsqrtss xmm0, xmm0, xmm0
        ret

et voici beaucoup de choses inutiles vinsertps instructions

Exemple de travail : https://godbolt.org/g/q1BQym

La convention d'appel x64 par défaut transmet les arguments des fonctions à virgule flottante dans les registres XMM, de sorte que ces vmovss y vinsertps devraient être éliminées. Pourquoi les compilateurs mentionnés les émettent-ils encore ? Est-il possible de s'en débarrasser sans assemblage en ligne ?

J'ai également essayé d'utiliser _mm_cvtss_f32 au lieu de _mm_store_ss et de multiples conventions d'appel, mais rien n'a changé.

2voto

plasmacel Points 505

Je rédige cette réponse en me basant sur les commentaires, certaines discussions et mes propres expériences.

Comme Ross Ridge l'a fait remarquer dans les commentaires, le compilateur n'est pas assez intelligent pour reconnaître que seul l'élément à virgule flottante le plus bas du registre XMM est utilisé, et il met donc à zéro les trois autres éléments avec les valeurs vxorps vinsertps des instructions. C'est absolument inutile, mais que pouvez-vous faire ?

Il convient de noter que clang 3.9 fait un bien meilleur travail que GCC 6.2 (ou snapshot actuel de 7.0) pour générer l'assemblage pour les intrinsèques d'Intel, puisqu'il n'échoue que dans les cas suivants _mm_fmadd_ss dans mon exemple. J'ai testé d'autres intrinsèques aussi et dans la plupart des cas clang a fait un travail parfait pour émettre des instructions uniques.

Que pouvez-vous faire ?

Vous pouvez utiliser la norme <cmath> avec l'espoir qu'elles soient définies comme intrinsèques au compilateur si des instructions CPU appropriées sont disponibles.

Ce n'est pas suffisant

Les compilateurs, comme CCG implémentent ces fonctions avec une gestion spéciale des NaN et des infinis. Ainsi, en plus des fonctions intrinsèques, elles peuvent effectuer des comparaisons, des branchements, et éventuellement des opérations d'optimisation. errno la gestion des drapeaux.

Drapeaux de compilateur -fno-math-errno -fno-trapping-math aider CCG y clang pour éliminer les cas particuliers supplémentaires de la virgule flottante et errno afin qu'ils puissent émettre des instructions uniques si possible : https://godbolt.org/g/LZJyaB .

Vous pouvez obtenir la même chose avec -ffast-math puisqu'il inclut également les drapeaux ci-dessus, mais il comprend bien plus que cela et ces dernières (comme les optimisations mathématiques non sécurisées) ne sont probablement pas souhaitées.

Malheureusement, ce n'est pas une solution portable. Elle fonctionne dans la plupart des cas (voir le lien godbolt), mais vous dépendez toujours de l'implémentation.

Quoi de plus

Vous pouvez encore utiliser l'assemblage en ligne, qui n'est pas non plus portable, est beaucoup plus délicat et comporte beaucoup plus d'éléments à prendre en compte. Malgré cela, pour des instructions d'une ligne aussi simples, cela peut convenir.

Les choses à considérer :

1er CCG / clang y Visual Studio utilisent une syntaxe différente pour l'assemblage en ligne, et Visual Studio ne le permet pas en mode x64.

2ème Vous devez émettre des instructions codées VEX (variantes à 3 opérations, par exemple vsqrtss xmm0 xmm1 xmm2 ) pour les cibles AVX, et les variantes non codées EVX (2 op, par ex. sqrtss xmm0 xmm1 ) pour les processeurs pré-AVX. Les instructions codées VEX sont des instructions à 3 opérandes, elles offrent donc plus de liberté au compilateur pour l'optimisation. Pour en tirer profit, paramètres d'entrée/sortie du registre doit être réglé correctement. Donc quelque chose comme ci-dessous fait le travail.

#   if __AVX__
    asm("vsqrtss %1, %1, %0" :"=x"(x) : "x"(x));
#   else
    asm("sqrtss %1, %0" :"=x"(x) : "x"(x));
#   endif

Mais ce qui suit est une mauvaise technique pour VEX :

asm("vsqrtss %1, %1, %0" :"+x"(x));

Il peut céder à une instruction de déplacement inutile, vérifier https://godbolt.org/g/VtNMLL .

3ème Comme Peter Cordes l'a souligné, vous pouvez perdre élimination des sous-expressions communes (CSE) y pliage constant (propagation constante) pour les fonctions d'assemblage en ligne. Cependant, si l'asm inline n'est pas déclaré comme volatile le compilateur peut la traiter comme une fonction pure qui ne dépend que de ses entrées et effectuer l'élimination des sous-expressions communes, ce qui est formidable.

Comme l'a dit Pierre :

" Ne pas utiliser l'asm en ligne " n'est pas une règle absolue, c'est juste quelque chose que vous Il s'agit simplement d'un élément dont vous devez être conscient et que vous devez examiner attentivement avant de l'utiliser. Si l alternatives ne répondent pas à vos exigences, et que vous ne vous retrouvez pas avec cet l'inlining dans des endroits où il ne peut pas être optimisé, alors allez-y. allez-y.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X