Pourquoi est-floor() si lent?
J'ai écrit du code récemment (ISO/ANSI C), et a été surpris de la mauvaise performance qu'il a obtenus. Longue histoire courte, il s'est avéré que le coupable était le floor()
fonction. Non seulement il était lent, mais il n'a pas vectoriser (avec le compilateur Intel, aka ICL).
Voici quelques repères pour la réalisation de plancher pour toutes les cellules dans une matrice 2D:
VC: 0.10
ICL: 0.20
De la comparer à une distribution simple:
VC: 0.04
ICL: 0.04
Comment peut - floor()
être beaucoup plus lent qu'un simple cast?! Il fait essentiellement la même chose (à part pour les nombres négatifs).
2ème question: est-ce que quelqu'un sait d'un super-rapide floor()
mise en œuvre?
PS: Voici la boucle que j'ai été benchmarking:
void Floor(float *matA, int *intA, const int height, const int width, const int width_aligned)
{
float *rowA=NULL;
int *intRowA=NULL;
int row, col;
for(row=0 ; row<height ; ++row){
rowA = matA + row*width_aligned;
intRowA = intA + row*width_aligned;
#pragma ivdep
for(col=0 ; col<width; ++col){
/*intRowA[col] = floor(rowA[col]);*/
intRowA[col] = (int)(rowA[col]);
}
}
}
Vous devez vous connecter pour publier un commentaire.
Un couple de choses a faire plancher plus lent qu'un cast et de prévenir la vectorisation.
Le plus important:
étage pouvez modifier l'état global. Si vous passez une valeur qui est trop grande pour être représentée par un entier dans le format flottant, le errno variable est définie à EDOM. Un traitement spécial pour NaNs est fait ainsi. Tout ce comportement est pour les applications qui veulent détecter les cas de dépassement de capacité et de gérer la situation en quelque sorte (ne me demandez pas comment).
La détection de ces problématiques conditions n'est pas simple et représente plus de 90% du temps d'exécution de l'étage. La réelle arrondissement est bon marché et peut être incorporé/vectorisé. Aussi C'est beaucoup de code, d'inlining la totalité de l'étage fonction de votre programme de s'exécuter plus lentement.
Certains compilateurs ont de spécial les drapeaux de compilation qui permettent au compilateur d'optimiser la rarement utilisé c-règles standard. Par exemple GCC peut être dit que vous n'êtes pas intéressé dans errno à tous. Pour faire passer -fno-math-errno ou -ffast-math. La CPI et VC peuvent avoir les mêmes drapeaux du compilateur.
Btw - Vous pouvez lancer votre propre plancher de la fonction à l'aide de simples jette. Vous avez juste à gérer la positif et négatif des cas différemment. Qui peut être beaucoup plus rapide si vous n'avez pas besoin de la gestion spéciale des débordements et des NaNs.
Si vous êtes en train de convertir le résultat de la
floor()
opération à un int, et si vous n'êtes pas inquiet au sujet de dépassement, alors le code suivant est beaucoup plus rapide que(int)floor(x)
:static inline
au lieu deinline
si vous voulez mettre ceci dans un fichier d'en-tête - voir stackoverflow.com/a/10245969/48015Branche-moins de Plancher et de Plafond (meilleure utilisation de la pipiline) pas d'erreur, vérifiez
ou à l'aide de chaussée
floor
donne les réponses incorrectes pour les entiers négatifs etceil
réponses incorrectes pour ceux qui sont positifs.La réelle plus rapide de la mise en œuvre pour un grand tableau moderne sur les Processeurs x86 serait
floor
). En C, cela devrait être possible avecfenv
des trucs, ou_mm_getcsr
/_mm_setcsr
.boucle sur le tableau faisant
_mm_cvtps_epi32
sur SIMD vecteurs, la conversion de 4float
s d'entier de 32 bits en utilisant le mode d'arrondi courant. (Et à stocker le résultat des vecteurs de la destination.)cvtps2dq xmm0, [rdi]
est un seul micro-fusion uop sur n'importe quel PROCESSEUR Intel ou AMD depuis K10 ou Core 2. (https://agner.org/optimize/) Même pour les 256 bits AVX version, avec YMM vecteurs.Ce qui permet de charger + conversion de + rangement 1 SIMD vecteur de résultats par cycle d'horloge, aussi vite qu'avec la troncature. (SSE2 a un FP->int instruction de conversion pour la troncation, précisément parce qu'il est très souvent nécessaire par les compilateurs C. Dans les mauvais jours avec x87, même
(int)x
nécessaire de changer le x87 mode d'arrondi de troncature et puis retour.cvttps2dq
pour les paniers float->int avec la troncature (note de l'extrat
dans le mnémonique). Ou pour les scalaires, passant de XMM entier registres,cvttss2si
oucvttsd2si
scalairesdouble
de scalaire entier.Avec certains déroulement de la boucle et/ou de bons d'optimisation, cela devrait être possible sans les goulots d'étranglement sur le front-end, à seulement 1-par-horloge magasin de débit supposant l'absence de cache-miss goulets d'étranglement. (Et sur Intel avant de Skylake, également un goulot d'étranglement sur 1-par-horloge paniers de conversion de débit.) c'est à dire 16, 32, ou 64 octets par cycle, à l'aide de SSE2, AVX, ou AVX512.
Sans changer le mode d'arrondi courant, vous avez besoin SSE4.1
roundps
pour arrondir unfloat
à l'entier le plus prochefloat
en utilisant votre choix de modes d'arrondi. Ou vous pouvez utiliser l'une des astuces de la des spectacles dans d'autres réponses que de travail pour des flotteurs avec de petites suffisamment d'ampleur pour s'adapter à un entier signé de 32 bits, puisque c'est votre ultime destination de toute façon.)(Avec les bonnes options de compilation, comme
-fno-math-errno
, et le droit-march
ou-msse4
options, les compilateurs peuvent inlinefloor
à l'aide deroundps
, ou le scalaire et/ou de double précision équivalente, par exempleroundsd xmm1, xmm0, 1
, mais cela coûte 2 uops et dispose de 1 par 2 horloge débit sur Haswell scalaires ou des vecteurs. En fait, gcc8.2 sera inlineroundsd
pourfloor
même sans aucun fast-options de mathématiques, comme vous pouvez le voir sur la Godbolt compilateur explorer. Mais c'est avec-march=haswell
. Ce n'est malheureusement pas de référence pour x86-64, de sorte que vous devez l'activer si votre ordinateur prend en charge.)vcvtps2dq
dépend de la valeur de la MXCSR de contrôle et de registre d'état. Dans cet exemple l'ordre dex=_mm_cvtps_epi32(y);
et_MM_SET_ROUNDING_MODE(_MM_ROUND_NEAREST);
a été échangé par la cpi.#pragma STDC FENV_ACCESS ON
, si cela fonctionne pour toute les compilateurs. (Ne FENV_ACCESS pragma existe pas en C++11 et plus?). Et/ou essayer de la CPI options de compilation comme-fp-model strict
pour lui dire que vous modifiez le FP mode d'arrondi. (La CPI par défaut est-fp-model fast=1
.)Oui,
floor()
est extrêmement lent sur toutes les plates-formes, car il doit mettre en œuvre un lot de comportement de l'IEEE fp spec. Vous ne pouvez pas vraiment l'utiliser à l'intérieur des boucles.M'arrive d'utiliser une macro pour approximative étage():
Il ne se comporte pas exactement comme
floor()
: par exemple,floor(-1) == -1
maisPSEUDO_FLOOR(-1) == -2
, mais il est assez proche pour la plupart des utilisations.Un fait dépourvu de branches version qui nécessite une seule conversion entre virgule flottante entier et les domaines à un déplacement de la valeur
x
à tous positifs ou négatifs à la plage, fonte/truncate et déplacement de retour.Comme indiqué dans les commentaires, cette mise en œuvre se fonde sur la valeur temporaire
x +- offset
ne déborde pas.Sur les plates-formes 64 bits, le code d'origine à l'aide de int64_t intermédiaire de la valeur de trois instructions du noyau, la même disposition pour int32_t réduction de l'éventail de plancher/plafond, où
|x| < 0x40000000
--long
étant plus large queint
de justesse grâce à la gamme complète deint
résultats? Ce n'est pas le cas sur de nombreuses plates-formes 32 bits, et sur x86-64 Windows (un LLP64 ABI où int et long sont à la fois 32 bits). Alors peut-être que vous devriez utiliserlong long
. Mais encore une belle idée.double
->unsigned long
est un peu lent sur x86. godbolt.org/z/1UqaQw. x86-64 n'a pas d'instruction pour que, jusqu'à AVX512, seulement pourdouble
-> entier signé. Sur 32-bit x86 oùunsigned long
est un 32-bits type, x87fistp
peut faire FP -> 64-bits entier signé, et vous pouvez utiliser la moitié basse de ce queunsigned int
. Mais la troncature nécessite SSE3fisttp
ou de changer le mode d'arrondi. SSE2 ne peut pas faire de troncature en 32 bits entier non signé ou 64 bits entier signé soit. Les autres réponses sont probablement plus efficace.Casting n'est pas un appel de fonction, de sorte qu'il utilise le plus rapide des mécanismes (je crois qu'il peut utiliser les registres de traiter les valeurs).
Jon Bentley Programmation Perles a une grande revue d'optimisations possibles.
Rapide à double tour
Terminal journal
test custom_1 8.3837
test native_1 18.4989
test custom_2 8.36333
test native_2 18.5001
test custom_3 8.37316
test native_3 18.5012
Test
Résultat
Type coulée et l'utilisation de votre cerveau est environ 3 fois plus rapide que l'utilisation des fonctions natives.
round()
ne fonctionne pas. Vous devez soit utiliser une virgule flottante modulo pour vérifier si la partie décimale est supérieure à 0,5, ou vous pouvez utiliser la vieille(int) (double_value + 0.5)
truc pour exécuter l'arrondissement.