web-dev-qa-db-fra.com

Quel est le moyen le plus rapide de diviser un entier par 3?

int x = n / 3;  // <-- make this faster

// for instance

int a = n * 3; // <-- normal integer multiplication

int b = (n << 1) + n; // <-- potentially faster multiplication
34
Greg Dean

C'est le plus rapide que le compilateur l'optimise s'il peut en fonction du processeur de sortie.

int a;
int b;

a = some value;
b = a / 3;
61
KPexEA

Le gars qui a dit "laissez-le au compilateur" avait raison, mais je n'ai pas la "réputation" de le moder ou de commenter. J'ai demandé à GCC de compiler INT test (int a) {retour A/3; } Pour une IX86, puis désassemblé la sortie. Juste pour l'intérêt académique, ce qu'il fait est grossièrement Multipliez par 0x55555556, puis en prenant les 32 premiers bits du résultat 64 bits de cela. Vous pouvez démontrer cela avec vous-même avec:

 $ Ruby -E 'met (60000 * 0x55555556 >> 32)' [.____] 20000 [.____] $ Ruby -E '-E' met (72 * 0x55555556 >> 32) '[.____] 24 
 $ [.____]

La page Wikipedia sur - Division de Montgomery est difficile à lire, mais heureusement, les gars du compilateur l'ont fait pour ne pas avoir à.

122
Martin Dorey

Il y a un moyen plus rapide de le faire si vous connaissez les plages des valeurs, par exemple, si vous divisez un entier signé par 3 et que vous connaissez la plage de la valeur à diviser est de 0 à 768, vous pouvez la multiplier par un facteur et le transférer à gauche par une puissance de 2 à ce facteur divisé par 3.

par exemple.

Gamme 0 -> 768

vous pouvez utiliser le décalage de 10 bits, ce qui par la multiplication 1024, vous voulez diviser par 3 afin que votre multiplicateur doit être 1024/3 = 341,

donc, vous pouvez maintenant utiliser (x * 341) >> 10
[.____] (Assurez-vous que le décalage est un décalage signé si vous utilisez des entiers signés), assurez-vous également que le décalage est un changement réellement et non un petit rouleau

Cela divisera efficacement la valeur 3 et fonctionnera à environ 1,6 fois la vitesse comme une fracture naturelle par 3 sur une CPU standard X86/X64.

Bien sûr, la seule raison pour laquelle vous pouvez faire cette optimisation lorsque le dévers du compilateur est parce que le compilateur ne connaît pas la portée maximale de X et ne peut donc pas prendre cette décision, mais vous la boîte programmeur.

Parfois, il peut même être plus bénéfique pour déplacer la valeur dans une plus grande valeur et faire la même chose, à savoir. Si vous avez un INT de grande gamme, vous pouvez en faire une valeur de 64 bits, puis effectuez la multiplication et la déplacement au lieu de diviser par 3.

Je devais le faire récemment pour accélérer le traitement de l'image, je devais trouver la moyenne de 3 canaux de couleur, chaque canal de couleur avec une plage d'octets (0 à 255). rouge vert et bleu.

Au début, je viens de simplement utiliser:

avg = (r + g + b)/3;

(SO R + G + B a un maximum de 768 et un minimum de 0, car chaque canal est un octet 0 - 255)

Après des millions d'itérations, toute l'opération a pris 36 millisecondes.

J'ai changé la ligne pour:

avg = (r + g + b) * 341 >> 10;

Et qu'il a fallu jusqu'à 22 millisecondes, son incroyable ce qui peut être fait avec un peu d'ingéniosité.

Cette vitesse est survenue en C # même si j'avais des optimisations activées et exécutait le programme de manière native sans débogage et non par l'IDE.

22
Aaron Murgatroyd

Voir Comment diviser par Pour une discussion prolongée de la division plus efficace de 3, axée sur les opérations arithmétiques du FPGA.

Également pertinent:

11
Jay

En fonction de votre plate-forme et en fonction de votre compilateur C, une solution indigène comme uniquement en utilisant

y = x / 3

Peut être rapide ou peut être terriblement lent (même si la division est entièrement faite dans le matériel, s'il est fait à l'aide d'une instruction DIV, cette instruction est d'environ 3 à 4 fois plus lente qu'une multiplication des processeurs modernes). Très bons compilateurs C avec drapeaux d'optimisation activés peuvent optimiser cette opération, mais si vous voulez être sûr, vous feriez mieux de l'optimiser vous-même.

Pour l'optimisation, il est important d'avoir des nombres entier de taille connue. Dans C int n'a aucune taille connue (il peut varier selon la plate-forme et le compilateur!), Vous ferez donc mieux d'utiliser des entiers de taille fixe C99. Le code ci-dessous suppose que vous souhaitez diviser un entier non signé 32 bits par trois et que vous C compilateur Connaissez environ 64 bits numéros entier (REMARQUE: même sur une architecture de processeurs 32 bits, la plupart des compilateurs C peuvent gérer 64 bits d'entiers. Tout simplement bien):

static inline uint32_t divby3 (
    uint32_t divideMe
) {
    return (uint32_t)(((uint64_t)0xAAAAAAABULL * divideMe) >> 33);
}

Aussi fou que cela pourrait sonner, mais la méthode ci-dessus divise effectivement par 3. Tout ce dont il a besoin pour le faire est une multiplication unique 64 bits et un décalage (comme je l'ai dit que les multiplications peuvent être 3 à 4 fois plus rapides que les divisions de votre CPU ). Dans une application 64 bits, ce code sera beaucoup plus rapide que dans une application 32 bits (dans une application 32 bits multiplier deux numéros de 64 bits prenant 3 multiplications et 3 ajouts sur des valeurs 32 bits) - Cependant, cela pourrait être encore plus rapide qu'un Division sur une machine 32 bits.

D'autre part, si votre compilateur est très bon et connaît l'astuce Comment optimiser la division entière par une constante (Dernière GCC, je viens de vérifier), il générera le code ci-dessus de toute façon (GCC créera exactement ce code pour "/ 3" Si vous activez au moins niveau d'optimisation 1). Pour les autres compilateurs ... Vous ne pouvez pas compter ou vous attendre à ce qu'il utiliserait des astuces comme celle-ci, même si cette méthode est très bien documentée et mentionnée partout sur Internet.

Le problème est que cela ne fonctionne que pour des nombres constants, pas pour les variables. Vous devez toujours connaître le numéro magique (ici 0xaaaaaaaab) et les opérations correctes après la multiplication (décalages et/ou ajouts dans la plupart des cas) et les deux sont différents selon le numéro que vous souhaitez diviser et les deux prennent trop de temps de processeur pour Calculez-les à la volée (cela serait plus lent que la division matérielle). Cependant, il est facile pour un compilateur de calculer ceux-ci pendant la compilation (où une seconde de plus ou moins de compilation joue à peine un rôle).

10
Mecki

Et si vous vraiment Vous ne voulez pas multiplier ni diviser? Voici une approximation que je viens d'inventer. Cela fonctionne parce que (x/3) = (x/4) + (x/12). Mais depuis (x/12) = (x/4)/3, nous devons simplement répéter le processus jusqu'à ce que cela soit suffisamment bon.

#include <stdio.h>

void main()
{
    int n = 1000;
    int a,b;
    a = n >> 2;
    b = (a >> 2);
    a += b;
    b = (b >> 2);
    a += b;
    b = (b >> 2);
    a += b;
    b = (b >> 2);
    a += b;
    printf("a=%d\n", a);
}

Le résultat est de 330. Il pourrait être rendu plus précis à l'aide de B = ((B + 2) >> 2); rendre compte de l'arrondissement.

Si vous êtes est autorisé à multiplier, choisissez simplement une approximation appropriée pour (1/3), avec un diviseur de puissance de 2. Par exemple, N * (1/3) ~ = N * 43/128 = (N * 43) >> 7.

Cette technique est la plus utile dans Indiana.

3
Steve Hanov

Je ne sais pas si c'est plus rapide, mais si vous souhaitez utiliser un opérateur binaire pour effectuer une division binaire, vous pouvez utiliser la méthode de décalage et de soustraction décrite à cette page :

  • Fixer quotient à 0
  • Aligner les chiffres les plus grands dans le dividende et le diviseur
  • Répéter:
    • Si cette partie du dividende au-dessus du diviseur est supérieure ou égale au diviseur: [.____]
      • Puis soustrayez le diviseur de cette partie du dividende et
      • Concatienter 1 à la main droite du quotient
      • Sinon concatate 0 à la main droite du quotient
    • Déplacer le diviseur un endroit droit
  • Jusqu'à ce que le dividende soit inférieur au diviseur:
  • quotient est correct, le dividende est restant
  • ARRÊTER
2
Mark Cidade

Si vous voulez vraiment voir cet article sur Division entière , mais cela n'a qu'un mérite académique ... ce serait une demande intéressante qui devait en réalité avoir bénéficié de ce type d'astuce.

1
Rob Walker

Pour une réellement grande division entière (par exemple, des chiffres plus gros que 64 bits), vous pouvez représenter votre numéro en tant qu'ob [] et effectuer une division assez rapide en prenant deux chiffres à la fois et en les divisez par 3. Le reste fera partie des deux chiffres suivants. et ainsi de suite.

par exemple. 11004/3 Vous dites

11/3 = 3, reste restaucre = 2 (de 11-3 * 3)

20/3 = 6, reste = 2 (de 20-6 * 3)

20/3 = 6, reste = 2 (de 20-6 * 3)

24/3 = 8, reste = 0

d'où le résultat 668

internal static List<int> Div3(int[] a)
{
  int remainder = 0;
  var res = new List<int>();
  for (int i = 0; i < a.Length; i++)
  {
    var val = remainder + a[i];
    var div = val/3;

    remainder = 10*(val%3);
    if (div > 9)
    {
      res.Add(div/10);
      res.Add(div%10);
    }
    else
      res.Add(div);
  }
  if (res[0] == 0) res.RemoveAt(0);
  return res;
}
1
Carlo V. Dango

Calcul faciles ... au plus N itérations où N est votre nombre de bits:

uint8_t divideby3(uint8_t x)
{
  uint8_t answer =0;
  do
  {
    x>>=1;
    answer+=x;
    x=-x;
  }while(x);
  return answer;
}
0
Albert Gonzalez

Une approche de la table de recherche serait également plus rapide dans certaines architectures.

uint8_t DivBy3LU(uint8_t u8Operand)
{
   uint8_t ai8Div3 = [0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, ....];

   return ai8Div3[u8Operand];
}
0
Luke