web-dev-qa-db-fra.com

Quel est le moyen le plus rapide pour calculer le péché et le cos ensemble?

Je voudrais calculer le sinus et le cosinus d'une valeur ensemble (par exemple pour créer une matrice de rotation). Bien sûr, je pourrais les calculer séparément les uns après les autres comme a = cos(x); b = sin(x);, mais je me demande s'il existe un moyen plus rapide lorsque j'ai besoin des deux valeurs.

Modifier: Pour résumer les réponses jusqu'à présent:

  • Vlad a dit qu'il y avait la commande asm FSINCOS calculant les deux (presque en même temps qu'un appel à FSIN seul)

  • Comme Chi remarqué, cette optimisation est parfois déjà effectuée par le compilateur (lors de l'utilisation de drapeaux d'optimisation).

  • caf a souligné que les fonctions sincos et sincosf sont probablement disponibles et peuvent être appelées directement en incluant simplement math.h

  • tanascius l'approche de l'utilisation d'une table de correspondance est discutée. (Cependant, sur mon ordinateur et dans un scénario de référence, il s'exécute 3 fois plus vite que sincos avec presque la même précision pour les virgules flottantes 32 bits.)

  • Joel Goodwin lié à une approche intéressante d'une technique d'approximation extrêmement rapide avec une assez bonne précision (pour moi, c'est encore plus rapide que la recherche de table)

98
Danvil

Les processeurs Intel/AMD modernes ont l'instruction FSINCOS pour calculer simultanément les fonctions sinus et cosinus. Si vous avez besoin d'une forte optimisation, vous devriez peut-être l'utiliser.

Voici un petit exemple: http://home.broadpark.no/~alein/fsincos.html

Voici un autre exemple (pour MSVC): http://www.codeguru.com/forum/showthread.php?t=328669

Voici encore un autre exemple (avec gcc): http://www.allegro.cc/forums/thread/58847

J'espère que l'un d'eux vous aidera. (Je n'ai pas utilisé cette instruction moi-même, désolé.)

Comme ils sont pris en charge au niveau du processeur, je m'attends à ce qu'ils soient beaucoup plus rapides que les recherches de table.

Éditer:
Wikipedia suggère que FSINCOS a été ajouté à 387 processeurs, donc vous pouvez difficilement trouver un processeur qui ne le supporte pas.

Éditer:
la documentation d'Intel indique que FSINCOS est environ 5 fois plus lent que FDIV (c'est-à-dire la division en virgule flottante).

Éditer:
Veuillez noter que tous les compilateurs modernes n'optimisent pas le calcul du sinus et du cosinus dans un appel à FSINCOS. En particulier, mon VS 2008 ne l'a pas fait de cette façon.

Éditer:
Le premier exemple de lien est mort, mais il y a toujours une version sur la Wayback Machine .

51
Vlad

Les processeurs x86 modernes ont une instruction fsincos qui fera exactement ce que vous demandez - calculez sin et cos en même temps. Un bon compilateur d'optimisation doit détecter le code qui calcule sin et cos pour la même valeur et utiliser la commande fsincos pour l'exécuter.

Il a fallu quelques manipulations des drapeaux du compilateur pour que cela fonctionne, mais:

$ gcc --version
i686-Apple-darwin9-gcc-4.0.1 (GCC) 4.0.1 (Apple Inc. build 5488)
Copyright (C) 2005 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ cat main.c
#include <math.h> 

struct Sin_cos {double sin; double cos;};

struct Sin_cos fsincos(double val) {
  struct Sin_cos r;
  r.sin = sin(val);
  r.cos = cos(val);
  return r;
}

$ gcc -c -S -O3 -ffast-math -mfpmath=387 main.c -o main.s

$ cat main.s
    .text
    .align 4,0x90
.globl _fsincos
_fsincos:
    pushl   %ebp
    movl    %esp, %ebp
    fldl    12(%ebp)
    fsincos
    movl    8(%ebp), %eax
    fstpl   8(%eax)
    fstpl   (%eax)
    leave
    ret $4
    .subsections_via_symbols

Tada, il utilise l'instruction fsincos!

38
Chi

Techniquement, vous pourriez y parvenir en utilisant des nombres complexes et formule d'Euler . Ainsi, quelque chose comme (C++)

complex<double> res = exp(complex<double>(0, x));
// or equivalent
complex<double> res = polar<double>(1, x);
double sin_x = res.imag();
double cos_x = res.real();

devrait vous donner sinus et cosinus en une seule étape. La façon dont cela est fait en interne est une question de compilateur et de bibliothèque utilisés. Cela pourrait (et pourrait) prendre plus de temps pour le faire de cette façon (juste parce que la formule d'Euler est principalement utilisée pour calculer le complexe exp en utilisant sin et cos - et non le dans l'autre sens) mais il pourrait y avoir une optimisation théorique possible.


Modifier

Les en-têtes dans <complex> pour GNU C++ 4.2 utilise des calculs explicites de sin et cos dans polar, donc ça n'a pas l'air trop bien pour optimisations à moins que le compilateur ne fasse de la magie (voir le -ffast-math et -mfpmath change comme écrit en réponse de Chi ).

13
Debilski

Lorsque vous avez besoin de performances, vous pouvez utiliser une table sin/cos précalculée (une table fera l'affaire, stockée sous forme de dictionnaire). Eh bien, cela dépend de la précision dont vous avez besoin (peut-être que la table serait trop grande), mais elle devrait être vraiment rapide.

13
tanascius

Vous pouvez calculer l'un ou l'autre puis utiliser l'identité:

cos (x)2 = 1 - sin (x)2

mais comme le dit @tanascius, une table précalculée est la solution.

12
Mitch Wheat

Si vous utilisez la bibliothèque GNU C, alors vous pouvez faire:

#define _GNU_SOURCE
#include <math.h>

et vous obtiendrez des déclarations des fonctions sincos(), sincosf() et sincosl() qui calculent les deux valeurs ensemble - probablement de la manière la plus rapide pour votre architecture cible.

8
caf

Il y a des choses très intéressantes sur cette page de forum, qui se concentre sur la recherche de bonnes approximations rapides: http://www.devmaster.net/forums/showthread.php?t=5784

Avis de non-responsabilité: Je n'ai utilisé aucun de ces trucs moi-même.

Mise à jour 22 février 2018: Wayback Machine est la seule façon de visiter la page d'origine maintenant: https://web.archive.org/web/20130927121234/http://devmaster.net/posts/9648/fast- et-sinus-cosinus précis

8
Joel Goodwin

De nombreuses bibliothèques de mathématiques C, comme l'indique caf, ont déjà sincos (). L'exception notable est MSVC.

  • Sun a eu sincos () depuis au moins 1987 (vingt-trois ans; j'ai une page de manuel sur papier)
  • HPUX 11 l'a eu en 1997 (mais n'est pas dans HPUX 10.20)
  • Ajouté à glibc dans la version 2.1 (février 1999)
  • Devenu un intégré dans gcc 3.4 (2004), __builtin_sincos ().

Et en ce qui concerne la recherche, Eric S. Raymond dans Art of Unix Programming (2004) (Chapter 12) dit explicitement que c'est une mauvaise idée (à l'heure actuelle):

"Un autre exemple est le précalcul de petites tables - par exemple, une table de sin (x) par degré pour optimiser les rotations dans un moteur graphique 3D prendra 365 × 4 octets sur une machine moderne. Avant que les processeurs ne soient suffisamment rapides pour que la mémoire demande la mise en cache , il s'agissait d'une optimisation évidente de la vitesse. De nos jours, il peut être plus rapide de recalculer à chaque fois plutôt que de payer pour le pourcentage d'échecs de cache supplémentaires causés par la table.

"Mais à l'avenir, cela pourrait se retourner à mesure que les caches s'agrandissent. Plus généralement, de nombreuses optimisations sont temporaires et peuvent facilement se transformer en pessimisations à mesure que les ratios de coûts changent. La seule façon de savoir est de mesurer et de voir." (extrait de Art of Unix Programming)

Mais, à en juger par la discussion ci-dessus, tout le monde n'est pas d'accord.

7
Joseph Quinsey

Je ne crois pas que les tables de recherche soient nécessairement une bonne idée pour ce problème. À moins que vos exigences de précision ne soient très faibles, la table doit être très grande. Et les processeurs modernes peuvent faire beaucoup de calculs pendant qu'une valeur est extraite de la mémoire principale. Ce n'est pas une de ces questions auxquelles il est possible de répondre correctement par un argument (pas même le mien), de tester et de mesurer et d'examiner les données.

Mais je regarderais les implémentations rapides de SinCos que vous trouverez dans des bibliothèques telles que ACML d'AMD et MKL d'Intel.

5

Cet article montre comment construire un algorithme parabolique qui génère à la fois le sinus et le cosinus:

Astuce DSP: approximation parabolique simultanée du péché et du cos

http://www.dspguru.com/dsp/tricks/parabolic-approximation-of-sin-and-cos

3
Probes

Si vous êtes prêt à utiliser un produit commercial et calculez un certain nombre de calculs sin/cos en même temps (afin que vous puissiez utiliser des fonctions vectorisées), vous devriez vérifier Intel's Math Kernel Library.

Il a une fonction sincos

Selon cette documentation, il affiche en moyenne 13,08 horloges/élément sur le duo core 2 en mode haute précision, ce qui, je pense, sera encore plus rapide que fsincos.

3
Chi

Pour une approche créative, que diriez-vous d'étendre la série Taylor? Puisqu'ils ont des termes similaires, vous pouvez faire quelque chose comme le pseudo suivant:

numerator = x
denominator = 1
sine = x
cosine = 1
op = -1
fact = 1

while (not enough precision) {
    fact++
    denominator *= fact
    numerator *= x

    cosine += op * numerator / denominator

    fact++
    denominator *= fact
    numerator *= x

    sine += op * numerator / denominator

    op *= -1
}

Cela signifie que vous faites quelque chose comme ceci: en commençant par x et 1 pour sin et cosinus, suivez le modèle - soustrayez x ^ 2/2! du cosinus, soustrayez x ^ 3/3! du sinus, ajoutez x ^ 4/4! au cosinus, ajoutez x ^ 5/5! à sinus ...

Je n'ai aucune idée si ce serait performant. Si vous avez besoin de moins de précision que ce que vous offrent sin () et cos (), cela peut être une option.

2
Tesserex

Il y a une belle solution dans la bibliothèque CEPHES qui peut être assez rapide et vous pouvez ajouter/supprimer une précision assez flexible pour un peu plus/moins de temps CPU.

Rappelez-vous que cos (x) et sin (x) sont les parties réelles et imaginaires de exp (ix). Nous voulons donc calculer exp (ix) pour obtenir les deux. Nous précalculons exp (iy) pour certaines valeurs discrètes de y entre 0 et 2pi. On décale x dans l'intervalle [0, 2pi). Ensuite, nous sélectionnons le y le plus proche de x et écrivons
exp (ix) = exp (iy + (ix-iy)) = exp (iy) exp (i (x-y)).

Nous obtenons exp (iy) à partir de la table de recherche. Et puisque | x-y | est petite (au plus la moitié de la distance entre les valeurs y), la série Taylor convergera bien en quelques termes, nous utilisons donc cela pour exp (i (x-y)). Et puis nous avons juste besoin d'une multiplication complexe pour obtenir exp (ix).

Une autre propriété intéressante de ceci est que vous pouvez le vectoriser en utilisant SSE.

2
Jsl

Vous voudrez peut-être jeter un œil à http://gruntthepeon.free.fr/ssemath/ , qui propose une implémentation vectorisée SSE inspirée de la bibliothèque CEPHES. Elle a bonne précision (déviation maximale par rapport à sin/cos de l'ordre de 5e-8) et vitesse (surpasse légèrement les fsincos sur une base d'appel unique, et un gagnant clair sur plusieurs valeurs).

2
SleuthEye

Lorsque les performances sont essentielles pour ce genre de chose, il n'est pas rare d'introduire une table de recherche.

2
Tom Cabanski

Une approximation précise mais rapide de la fonction sin et cos simultanément, en javascript, peut être trouvée ici: http://danisraelmalta.github.io/Fmath/ (facilement importé en c/c ++)

1
user2781980

J'ai posté une solution impliquant inline ARM Assemblage capable de calculer le sinus et le cosinus de deux angles à la fois ici: sinus/cosinus rapide pour ARMv7 + NEON

1
jcayzac

Le compilateur MSVC peut utiliser les fonctions (internes) SSE2

 ___libm_sse2_sincos_ (for x86)
 __libm_sse2_sincos_  (for x64)

dans les versions optimisées si les indicateurs de compilateur appropriés sont spécifiés (au minimum/O2/Arch: SSE2/fp: fast). Les noms de ces fonctions semblent impliquer qu'elles ne calculent pas le péché et le cos séparés, mais les deux "en une seule étape".

Par exemple:

void sincos(double const x, double & s, double & c)
{
  s = std::sin(x);
  c = std::cos(x);
}

Assemblage (pour x86) avec/fp: rapide:

movsd   xmm0, QWORD PTR _x$[esp-4]
call    ___libm_sse2_sincos_
mov     eax, DWORD PTR _s$[esp-4]
movsd   QWORD PTR [eax], xmm0
mov     eax, DWORD PTR _c$[esp-4]
shufpd  xmm0, xmm0, 1
movsd   QWORD PTR [eax], xmm0
ret     0

L'assembly (pour x86) sans/fp: fast mais avec/fp: precise à la place (qui est la valeur par défaut) appelle des sin et cos séparés:

movsd   xmm0, QWORD PTR _x$[esp-4]
call    __libm_sse2_sin_precise
mov     eax, DWORD PTR _s$[esp-4]
movsd   QWORD PTR [eax], xmm0
movsd   xmm0, QWORD PTR _x$[esp-4]
call    __libm_sse2_cos_precise
mov     eax, DWORD PTR _c$[esp-4]
movsd   QWORD PTR [eax], xmm0
ret     0

So/fp: fast est obligatoire pour l'optimisation sincos.

Mais veuillez noter que

___libm_sse2_sincos_

n'est peut-être pas aussi précis que

__libm_sse2_sin_precise
__libm_sse2_cos_precise

en raison du "précis" manquant à la fin de son nom.

Sur mon système "légèrement" plus ancien (Intel Core 2 Duo E6750) avec le dernier compilateur MSVC 2019 et les optimisations appropriées, mon benchmark montre que l'appel sincos est environ 2,4 fois plus rapide que les appels sin et cos séparés.

0
x y

Avez-vous pensé à déclarer des tables de recherche pour les deux fonctions? Vous auriez encore à "calculer" sin (x) et cos (x), mais ce serait décidément plus rapide, si vous n'avez pas besoin d'un haut degré de précision.

0
Frank Shearar