120 votes

Calculs à virgule flottante vs entier sur du matériel moderne

Je suis en train de faire quelques critiques pour les performances en C++, et nous sommes actuellement en utilisant des calculs avec des entiers pour les problèmes qui sont de nature à virgule flottante parce que "son plus rapide". Cela provoque beaucoup de problèmes gênants et ajoute beaucoup de gênant code.

Maintenant, je me souviens avoir lu sur la façon dont les calculs en virgule flottante ont été si lent à environ vers l'386 jours, où je crois (IIRC) qu'il y avait une option de co-proccessor. Mais sûrement aujourd'hui avec d'autant plus complexes et les plus puissants Processeurs, il ne fait aucune différence dans la "vitesse" si faire de point flottant ou entier de calcul? Surtout étant donné que les temps de calcul minuscule par rapport à quelque chose comme causant un pipeline de décrochage ou chercher quelque chose dans la mémoire principale?

Je sais que la bonne réponse est la référence sur le matériel cible, ce serait un bon moyen de tester cela? J'ai écrit deux petits programmes C++ et comparer les temps d'exécution avec le "temps" sur Linux, mais le temps d'exécution est trop variable (n'a pas d'aide, je suis en cours d'exécution sur un serveur virtuel). Court de passer toute ma journée consécutive, des centaines de points de référence, faire des graphiques, etc. est-il quelque chose que je peux faire pour obtenir un raisonnable test de la vitesse relative? Toutes les idées ou les pensées? Suis-je complètement tort?

Les programmes que j'ai utilisé comme suit, ils ne sont pas identiques par tous les moyens:

#include <iostream>
#include <cmath>
#include <cstdlib>
#include <time.h>

int main( int argc, char** argv )
{
    int accum = 0;

    srand( time( NULL ) );

    for( unsigned int i = 0; i < 100000000; ++i )
    {
        accum += rand( ) % 365;
    }
    std::cout << accum << std::endl;

    return 0;
}

Programme 2:

#include <iostream>
#include <cmath>
#include <cstdlib>
#include <time.h>

int main( int argc, char** argv )
{

    float accum = 0;
    srand( time( NULL ) );

    for( unsigned int i = 0; i < 100000000; ++i )
    {
        accum += (float)( rand( ) % 365 );
    }
    std::cout << accum << std::endl;

    return 0;
}

Merci à l'avance!

Edit: La plate-forme je me soucie est régulier x86 ou x86-64 fonctionnant sur Linux de bureau et les machines Windows.

Edit 2(collé à partir d'un commentaire ci-dessous): Nous avons une vaste base de code actuellement. Vraiment j'en suis venu à l'encontre de la généralisation que nous "ne doit pas utiliser float depuis entier de calcul est plus rapide" - et je suis à la recherche d'un moyen (si cela est encore vrai) pour réfuter cette prise en charge généralisée. Je me rends compte qu'il serait impossible de prédire le résultat pour nous, bref de faire tout le travail et le profilage par la suite.

De toute façon, merci à tous pour vos excellentes réponses et de l'aide. N'hésitez pas à ajouter d'autre chose :).

59voto

vladr Points 34562

Par exemple (en plus petit nombre, sont plus rapides),

64-bit Intel Xeon X5550 @ 2.67 GHz, gcc 4.1.2 -O3

short add/sub: 1.005460 [0]
short mul/div: 3.926543 [0]
long add/sub: 0.000000 [0]
long mul/div: 7.378581 [0]
long long add/sub: 0.000000 [0]
long long mul/div: 7.378593 [0]
float add/sub: 0.993583 [0]
float mul/div: 1.821565 [0]
double add/sub: 0.993884 [0]
double mul/div: 1.988664 [0]

32-bit Dual Core AMD Opteron(tm) Processor 265 @ 1.81 GHz, gcc 3.4.6 -O3

short add/sub: 0.553863 [0]
short mul/div: 12.509163 [0]
long add/sub: 0.556912 [0]
long mul/div: 12.748019 [0]
long long add/sub: 5.298999 [0]
long long mul/div: 20.461186 [0]
float add/sub: 2.688253 [0]
float mul/div: 4.683886 [0]
double add/sub: 2.700834 [0]
double mul/div: 4.646755 [0]

Comme Dan l'a souligné, encore une fois vous de normaliser la fréquence de l'horloge (qui peut être trompeur dans lui-même dans le pipeline dessins), les résultats varient énormément basé sur l'architecture du PROCESSEUR (individuel ALU/FPU performance, ainsi que de réelles nombre d'ALUs/Upc disponible par cœur dans superscalar dessins qui influe sur la façon dont de nombreuses opérations indépendantes, peut exécuter en parallèle -- le dernier facteur n'est pas exercé par le code ci-dessous que toutes les opérations ci-dessous sont successivement dépendante.)

Pauvre homme FPU/ALU opération de référence:

#include <stdio.h>
#ifdef _WIN32
#include <sys/timeb.h>
#else
#include <sys/time.h>
#endif
#include <time.h>

double
mygettime(void) {
# ifdef _WIN32
  struct _timeb tb;
  _ftime(&tb);
  return (double)tb.time + (0.001 * (double)tb.millitm);
# else
  struct timeval tv;
  if(gettimeofday(&tv, 0) < 0) {
    perror("oops");
  }
  return (double)tv.tv_sec + (0.000001 * (double)tv.tv_usec);
# endif
}

template< typename Type >
void my_test(const char* name) {
  Type v  = 0;
  // Do not use constants or repeating values
  //  to avoid loop unroll optimizations.
  // All values >0 to avoid division by 0
  // Perform ten ops/iteration to reduce
  //  impact of ++i below on measurements
  Type v0 = (Type)(rand() % 256)/16 + 1;
  Type v1 = (Type)(rand() % 256)/16 + 1;
  Type v2 = (Type)(rand() % 256)/16 + 1;
  Type v3 = (Type)(rand() % 256)/16 + 1;
  Type v4 = (Type)(rand() % 256)/16 + 1;
  Type v5 = (Type)(rand() % 256)/16 + 1;
  Type v6 = (Type)(rand() % 256)/16 + 1;
  Type v7 = (Type)(rand() % 256)/16 + 1;
  Type v8 = (Type)(rand() % 256)/16 + 1;
  Type v9 = (Type)(rand() % 256)/16 + 1;

  double t1 = mygettime();
  for (size_t i = 0; i < 100000000; ++i) {
    v += v0;
    v -= v1;
    v += v2;
    v -= v3;
    v += v4;
    v -= v5;
    v += v6;
    v -= v7;
    v += v8;
    v -= v9;
  }
  // Pretend we make use of v so compiler doesn't optimize out
  //  the loop completely
  printf("%s add/sub: %f [%d]\n", name, mygettime() - t1, (int)v&1);
  t1 = mygettime();
  for (size_t i = 0; i < 100000000; ++i) {
    v /= v0;
    v *= v1;
    v /= v2;
    v *= v3;
    v /= v4;
    v *= v5;
    v /= v6;
    v *= v7;
    v /= v8;
    v *= v9;
  }
  // Pretend we make use of v so compiler doesn't optimize out
  //  the loop completely
  printf("%s mul/div: %f [%d]\n", name, mygettime() - t1, (int)v&1);
}

int main() {
  my_test< short >("short");
  my_test< long >("long");
  my_test< long long >("long long");
  my_test< float >("float");
  my_test< double >("double");

  return 0;
}

44voto

Dan Points 542

Hélas, je ne peux que vous donner un "ça dépend" de réponse...

De mon expérience, il ya beaucoup, beaucoup de variables à la performance...en particulier entre les entiers et à virgule flottante en mathématiques. Il varie fortement d'processeur processeur (même au sein de la même famille comme x86) parce que les différents processeurs différents "pipeline" de long. Aussi, certaines opérations sont généralement très simples (comme ailleurs) et ont une accélération de la route par le processeur, et d'autres (comme la division) prendre beaucoup, beaucoup plus.

Le grand autre variable est là que les données résident. Si vous n'avez que quelques valeurs à ajouter, puis toutes les données peuvent résider dans le cache, où ils peuvent être rapidement envoyé sur le CPU. Une très, très lent opération de virgule flottante qui dispose déjà de données dans le cache sera plusieurs fois plus rapide que sur un nombre entier d'opération où un nombre entier doit être copiés de la mémoire système.

Je suppose que vous vous poser cette question parce que vous travaillez sur une critique pour les performances de l'application. Si vous développez pour l'architecture x86, et vous avez besoin de plus de performances, vous voudrez peut-être regarder dans l'aide de l'ESS extensions. Cela peut grandement accélérer virgule flottante simple précision arithmétique, la même opération peut être effectuée sur plusieurs données à la fois, plus il y a un autre* banque de registres de l'ESS opérations. (J'ai remarqué dans votre deuxième exemple que vous avez utilisé "flotter" au lieu de "double", me faisant penser que vous êtes en utilisant un seul de mathématiques de précision).

*Remarque: l'Utilisation de l'ancien instructions MMX serait de ralentir les programmes, parce que ces vieux instructions en fait utilisé les mêmes registres que la FPU, ce qui rend impossible à utiliser à la fois la FPU et MMX en même temps.

31voto

Ben Voigt Points 151460

Il y a probablement une différence significative dans le vrai monde de vitesse entre le point fixe et de mathématiques à virgule flottante, mais l'théorique meilleur des cas, le débit de l'ALU vs FPU est complètement hors de propos. Au lieu de cela, le nombre d'entier et registres à virgule flottante (réel registres, de ne pas enregistrer des noms) sur votre architecture qui ne sont pas utilisées autrement par votre calcul (par exemple pour le contrôle en boucle), le nombre d'éléments de chaque type qui s'inscrivent dans une ligne de cache, les optimisations possibles en considérant les différentes sémantique pour l'entier vs calcul en virgule flottante, ces effets vont dominer. Les dépendances de données de votre algorithme joue ici un rôle important, de sorte qu'aucune comparaison générale prédit que l'écart de performances sur votre problème.

Par exemple, integer l'addition est commutative, donc si le compilateur voit une boucle comme vous avez utilisé pour un indice de référence, en supposant que les données aléatoires a été préparé à l'avance, de sorte qu'il ne serait pas masquer les résultats), il peut dérouler la boucle et de calculer les sommes partielles sans dépendances, puis les ajouter lorsque la boucle se termine. Mais avec virgule flottante, le compilateur doit faire les opérations dans le même ordre que vous avez demandé (vous avez de la séquence de points de il y, de sorte que le compilateur doit garantir le même résultat, ce qui les empêche de réorganisation) il y a donc une forte dépendance à l'égard de chaque addition sur le résultat de la précédente.

Vous êtes susceptible d'ajustement en plus des opérandes entiers dans le cache à la fois. Si le point fixe de la version pourrait surpasser la version float par un ordre de grandeur, même sur une machine où la FPU a théoriquement un débit plus élevé.

20voto

Potatoswatter Points 70305

Ajout est beaucoup plus rapide que `` , de sorte que votre programme est inutile (et surtout).

Vous devez identifier les points chauds de performance et de modifier progressivement votre programme. On dirait que vous avez des problèmes avec votre environnement de développement qui devront être résolus d’abord. Il est impossible d’exécuter votre programme sur votre PC pour un ensemble de petits problèmes ?

Généralement, tentant d’emplois FP avec l’arithmétique sur les entiers est une recette pour ralentir.

8voto

jcoder Points 14982

Deux points à prendre en considération -

Les machines modernes peuvent se chevaucher instructions, les exécuter en parallèle et de les réorganiser afin d'utiliser au mieux le matériel. Et aussi, toute virgule flottante programme est susceptible d'avoir d'importantes entier travaillent trop, même si c'est seulement pour le calcul des indices dans les tableaux, compteur de boucle, etc. donc, même si vous avez une lente instruction à virgule flottante, il peut bien être en cours d'exécution sur une autre peu de matériel coïncidé avec certains de l'entier de travail. Mon point étant que, même si les instructions en virgule flottante sont lents qu'un entier, l'ensemble de votre programme peut s'exécuter plus rapidement, car il peut faire usage de plus de matériel.

Comme toujours, la seule façon d'en être sûr est de profil de votre programme.

Deuxième point est que la plupart des Processeurs, de nos jours, instructions SIMD pour virgule flottante qui peut fonctionner sur plusieurs valeurs à virgule flottante tous en même temps. Par exemple, vous pouvez charger 4 flotteurs en un seul ESS s'inscrire et le faire 4 multiplications sur toutes en même temps. Si vous pouvez réécrire certaines parties de votre code pour utiliser les instructions SSE ensuite, il semble probable qu'il sera plus rapide que pour un entier version. Visual c++ fournit un compilateur fonctions intrinsèques pour ce faire, voir http://msdn.microsoft.com/en-us/library/x5c07e2a(v=VS.80).aspx pour obtenir certaines informations.

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