web-dev-qa-db-fra.com

Code le plus rapide C / C ++ pour sélectionner la médiane dans un ensemble de 27 valeurs de point flottant

C'est l'algorithme de sélection bien connu. Voir http: //fr.wikipedia.org/wiki/selection_algorithm .

J'en ai besoin pour trouver la valeur médiane d'un ensemble de valeurs de voxel 3x3x3. Étant donné que le volume est constitué d'un milliard de voxels et l'algorithme est récursif, il est préférable d'être un peu rapide. En général, on peut s'attendre à ce que les valeurs soient relativement proches.

L'algorithme connu le plus rapide que j'ai essayé jusqu'à présent utilise la fonction de partition de tri rapide. J'aimerais savoir s'il y a un plus rapide.

J'ai "inventé" 20% plus rapide à l'aide de deux tas, mais attendez-vous encore plus rapide à l'aide d'un hachage. Avant de la mettre en œuvre, j'aimerais savoir si une solution rapide Blitz existe déjà là-bas.

Le fait que j'utilise des flotteurs n'aurais pas d'importance car ils peuvent être considérés comme un entier non signé après l'inversion du piqué de signalisation. La commande sera préservée.

EDIT: Benchmark and Code source s'est déplacé dans une réponse distincte comme suggérée par Davy Landman. Voir ci-dessous pour la réponse de Chmike.

[~ # ~] Edit [~ # ~ ~] : L'algorithme le plus efficace jusqu'à présent a été référencé ci-dessous par Boojum comme un lien vers le Filtrage rapide médian et bilatéral papier qui est maintenant la réponse à cette question. La première idée intelligente de cette méthode consiste à utiliser la sorte RADIX, la seconde consiste à combiner la recherche médiane de pixels adjacents qui partagent beaucoup de pixels.

39
chmike

Comme cela ressemble à vous, comme vous effectuez un filtre médian sur un grand nombre de données de volume, vous voudrez peut-être jeter un coup d'œil sur le document Médian rapide et filtrage bilatéral de Siggraph 2006. Ce document traite avec 2D Traitement d'image, mais vous pourrez peut-être adapter l'algorithme pour les volumes 3D. Si rien d'autre, cela pourrait vous donner quelques idées sur la manière de reculer et de regarder le problème d'une perspective légèrement différente.

14
Boojum

L'algorithme de sélection est le temps linéaire (O (n)). Complexité-sage Vous ne pouvez pas faire mieux que le temps linéaire, car il faut du temps linéaire pour lire toutes les données. Donc, vous n'auriez pas pu faire quelque chose qui est plus rapide de complexité. Peut-être avez-vous quelque chose qui est un facteur constant plus rapidement sur certaines intrants? Je doute que cela ferait une grande partie de la différence.

C++ inclut déjà l'algorithme de sélection de temps linéaire. Pourquoi ne pas simplement l'utiliser?

std::vector<YourType>::iterator first = yourContainer.begin();
std::vector<YourType>::iterator last = yourContainer.end();
std::vector<YourType>::iterator middle = first + (last - first) / 2;
std::nth_element(first, middle, last); // can specify comparator as optional 4th arg
YourType median = *middle;

EDIT: techniquement, ce n'est que la médiane d'un conteneur de longueur impaire. Pour une durée uniforme, il obtiendra la médiane "supérieure". Si vous souhaitez la définition traditionnelle de la médiane pendant une longueur pair, vous devrez peut-être l'exécuter deux fois, une fois pour chacun des deux "intermédiaires" à first + (last - first) / 2 et first + (last - first) / 2 - 1, puis les moyennes ou quelque chose.

29
newacct

Edit: Je dois m'excuser. Le code ci-dessous était faux. J'ai le code fixe, mais il faut trouver un ICC compilateur pour refaire les mesures.

Les résultats de référence des algorithmes considérés jusqu'à présent

Pour le protocole et une description courte des algorithmes voient ci-dessous. La première valeur est une heure moyenne (secondes) sur 200 séquences différentes et deuxième valeur est STDDEV.

HeapSort     : 2.287 0.2097
QuickSort    : 2.297 0.2713
QuickMedian1 : 0.967 0.3487
HeapMedian1  : 0.858 0.0908
NthElement   : 0.616 0.1866
QuickMedian2 : 1.178 0.4067
HeapMedian2  : 0.597 0.1050
HeapMedian3  : 0.015 0.0049 <-- best

Protocole: génère 27 flotteurs aléatoires à l'aide de bits aléatoires obtenus à partir de Rand (). Appliquez chaque algorithme 5 millions de fois d'une ligne (y compris la copie de la matrice antérieure) et calculez la moyenne et STDDEV Plus de 200 séquences aléatoires. Code C++ compilé avec ICC -S -O3 et exécutez Intel E8400 avec 8 Go DDR3.

Algorithmes:

Heapsort: une sorte de séquence totale à l'aide de la tri des tas et de la valeur moyenne. Mise en œuvre naïve à l'aide de l'accès à l'indice.

Quicksort: Série complète en place en utilisant un tri rapide et une valeur moyenne. Mise en œuvre naïve à l'aide de l'accès à l'indice.

Quickmedian1: algorithme de sélection rapide avec échange. Mise en œuvre naïve à l'aide de l'accès à l'indice.

Heapmedian1: Méthode de tas équilibré en place avec un échange préalable. Mise en œuvre naïve à l'aide de l'accès à l'indice.

Nthelement: Utilise l'algorithme STL NT_Element. Les données sont copiées dans le vecteur en utilisant memcpy (vct.data (), rndval, ...);

Quickmedian2: utilise un algorithme de sélection rapide avec des pointeurs et copier deux tampons pour éviter l'échange. Basé sur la proposition de msalters.

Heapmedian2: variante de mon algorithme inventé à l'aide de deux tas avec des têtes partagées. Le tas de gauche a la plus grande valeur que la tête, le droit a la plus petite valeur que la tête. Initialiser avec la première valeur en tant que tête courante et première valeur médiane devinez. Ajoutez des valeurs ultérieures au tas de gauche si plus petit que la tête, sinon sur le tas de droite, jusqu'à ce que l'un des tas soit plein. Il est plein quand il contient 14 valeurs. Ensuite, considérons uniquement le tas complet. Si c'est le bon tas, pour toutes les valeurs plus grandes que la tête, la tête de la tête et l'insertion. Ignorer toutes les autres valeurs. Si c'est le tas de gauche, pour toutes les valeurs plus petites que la tête, la tête de la tête et insérez-la dans le tas. Ignorer toutes les autres valeurs. Lorsque toutes les valeurs ont été poursuivies, la tête courante est la valeur médiane. Il utilise l'indice d'entier en tableau. La version utilisant des pointeurs (64 bits) semblait être presque deux fois plus lentes (~ 1s).

Heapmedian3: même algorithme que ThePmedian2 mais optimisé. Il utilise un index de caractère non signé, évite d'échange de valeur et de diverses autres petites choses. Les valeurs moyennes et STDDEV sont calculées sur 1000 séquences aléatoires. Pour NTH_Element, j'ai mesuré 0,508S et un STDDEV de 0,159537 avec les mêmes 1000 séquences aléatoires. Heapmedian3 est donc 33 fois plus rapide que la fonction STL NT_Element. Chaque valeur médiane renvoyée est vérifiée contre la valeur médiane renvoyée par Hepsort et toutes correspondent. Je doute que la méthode utilisant un hachage puisse être nettement plus rapide.

Modifier 1: Cet algorithme peut être encore optimisé. La première phase où les éléments sont expédiés dans le tas de gauche ou de droite sur la base du résultat de comparaison n'a pas besoin de tas. Il suffit de simplement ajouter des éléments à deux séquences non ordonnées. La phase 1 s'arrête dès qu'une séquence est pleine, ce qui signifie qu'il contient 14 éléments (y compris la valeur médiane). La deuxième phase commence par le recouvrement de la séquence complète, puis procédez comme décrit dans l'algorithme HEPMEDIAN3. Je vais fournir le nouveau code et référence dès que possible.

Edit 2: J'ai mis en œuvre et a expliqué l'algorithme optimisé. Mais il n'y a pas de différence de performance significative par rapport à Heapmedian3. Il est même légèrement plus lent sur la moyenne. Les résultats montrés sont confirmés. Il pourrait y avoir des ensembles beaucoup plus grands. Notez également que je choisis simplement la première valeur que la supposition médiane initiale. Comme suggéré, on pourrait tirer parti du fait que nous recherchons une valeur médiane dans des ensembles de valeur "chevauchement". L'utilisation de la médiane de l'algorithme médian aiderait à choisir une bien meilleure valeur médiane initiale.


code source de heapmedian

// return the median value in a vector of 27 floats pointed to by a
float heapMedian3( float *a )
{
   float left[14], right[14], median, *p;
   unsigned char nLeft, nRight;

   // pick first value as median candidate
   p = a;
   median = *p++;
   nLeft = nRight = 1;

   for(;;)
   {
       // get next value
       float val = *p++;

       // if value is smaller than median, append to left heap
       if( val < median )
       {
           // move biggest value to the heap top
           unsigned char child = nLeft++, parent = (child - 1) / 2;
           while( parent && val > left[parent] )
           {
               left[child] = left[parent];
               child = parent;
               parent = (parent - 1) / 2;
           }
           left[child] = val;

           // if left heap is full
           if( nLeft == 14 )
           {
               // for each remaining value
               for( unsigned char nVal = 27 - (p - a); nVal; --nVal )
               {
                   // get next value
                   val = *p++;

                   // if value is to be inserted in the left heap
                   if( val < median )
                   {
                       child = left[2] > left[1] ? 2 : 1;
                       if( val >= left[child] )
                           median = val;
                       else
                       {
                           median = left[child];
                           parent = child;
                           child = parent*2 + 1;
                           while( child < 14 )
                           {
                               if( child < 13 && left[child+1] > left[child] )
                                   ++child;
                               if( val >= left[child] )
                                   break;
                               left[parent] = left[child];
                               parent = child;
                               child = parent*2 + 1;
                           }
                           left[parent] = val;
                       }
                   }
               }
               return median;
           }
       }

       // else append to right heap
       else
       {
           // move smallest value to the heap top
           unsigned char child = nRight++, parent = (child - 1) / 2;
           while( parent && val < right[parent] )
           {
               right[child] = right[parent];
               child = parent;
               parent = (parent - 1) / 2;
           }
           right[child] = val;

           // if right heap is full
           if( nRight == 14 )
           {
               // for each remaining value
               for( unsigned char nVal = 27 - (p - a); nVal; --nVal )
               {
                   // get next value
                   val = *p++;

                   // if value is to be inserted in the right heap
                   if( val > median )
                   {
                       child = right[2] < right[1] ? 2 : 1;
                       if( val <= right[child] )
                           median = val;
                       else
                       {
                           median = right[child];
                           parent = child;
                           child = parent*2 + 1;
                           while( child < 14 )
                           {
                               if( child < 13 && right[child+1] < right[child] )
                                   ++child;
                               if( val <= right[child] )
                                   break;
                               right[parent] = right[child];
                               parent = child;
                               child = parent*2 + 1;
                           }
                           right[parent] = val;
                       }
                   }
               }
               return median;
           }
       }
   }
} 
21
chmike

La question ne peut pas être facilement répondue à la simple raison que les performances d'un algorithme par rapport à une autre dépendent autant que la combinaison de compilateur/processeur/structure de données que sur l'algorithme lui-même, comme vous le savez sûrement

Par conséquent, votre approche d'essayer quelques d'entre eux semble suffisamment bonne. Et oui, Quicksort devrait être assez rapide. Si vous ne l'avez pas fait, vous voudrez peut-être essayer INSERTIONSORT qui fonctionne souvent mieux sur de petits ensembles de données. Cela dit, il suffit de régler un algo de tri qui fait assez vite le travail. Vous n'obtenez généralement pas 10 fois plus vite que vous choisissez simplement l'algo "à droite".

Pour obtenir des économies substantielles, la meilleure façon est souvent d'utiliser plus de structure. Quelques idées qui ont travaillé pour moi dans le passé avec des problèmes à grande échelle:

  • Pouvez-vous préciser efficacement lors de la création des voxels et du stockage 28 au lieu de 27 flotteurs?

  • Une solution approximative est-elle assez bonne? Si tel est le cas, il suffit de regarder la médiane de, disons 9 valeurs, car "en général, on peut s'attendre à ce que les valeurs soient relativement proches". Ou vous pouvez le remplacer par la moyenne tant que les valeurs sont relativement proches.

  • Avez-vous vraiment besoin de la médiane pour toutes les milliards de voxels? Peut-être avez-vous un test facile si vous avez besoin de la médiane et que vous ne pouvez alors calculer que pour le sous-ensemble concerné.

  • Si rien d'autre aide: regarder le code ASM que le compilateur génère. Vous pourriez être capable d'écrire un code ASM sensiblement plus rapide (par exemple en effectuant tous les calculs à l'aide de registres).

Edit : Pour ce que ça vaut la peine, j'ai joint le code insertions (partielle) mentionné dans le commentaire ci-dessous (totalement non testé). Si numbers[] Est un tableau de taille N, et vous voulez que les plus petits P floats triés au début de la matrice, appelez partial_insertionsort<N, P, float>(numbers);. Par conséquent, si vous appelez partial_insertionsort<27, 13, float>(numbers);, numbers[13] Contiendra la médiane. Pour gagner une vitesse supplémentaire, vous devrez déplier la boucle tandis que. Comme indiqué ci-dessus, vous devez utiliser votre connaissance de la part des données (par exemple, les données sont déjà partiellement triées? Savez-vous les propriétés de la distribution des données? Je suppose que vous obtenez la dérive).

template <long i> class Tag{};

template<long i, long N, long P, typename T>
inline void partial_insertionsort_for(T a[], Tag<N>, Tag<i>)
{   long j = i <= P+1 ? i : P+1;  // partial sort
    T temp = a[i];
    a[i] = a[j];       // compiler should optimize this away where possible
    while(temp < a[j - 1] && j > 0)
    { a[j] = a[j - 1];
      j--;}
    a[j] = temp;
    partial_insertionsort_for<i+1,N,P,T>(a,Tag<N>(),Tag<i+1>());}

template<long i, long N, long P, typename T>
inline void partial_insertionsort_for(T a[], Tag<N>, Tag<N>){}

template <long N, long P, typename T>
inline void partial_insertionsort(T a[])
 {partial_insertionsort_for<0,N,P,T>(a, Tag<N>(), Tag<0>());}
13
stephan

Le plus algorithme susceptible d'être utilisé dans votre première tentative est juste nth_element; il vous donne à peu près ce que vous voulez directement. Il suffit de demander le 14 élément.

Sur votre deuxième tentative, l'objectif est de tirer parti de la taille des données fixes. Vous n'avez pas wnat d'allouer une mémoire à tous Duing votre algorithme. Alors, copiez vos valeurs Voxel à un tableau pré-alloué de 27 éléments. Choisir un pivot, et le copier dans le milieu d'une rangée d'éléments 53. Copier les valeurs restantes de chaque côté du pivot. Vous gardez deux pointeurs (float* left = base+25, *right=base+27). Il y a maintenant trois possibilités: le côté gauche est plus grande, le côté droit est plus grande, ou les deux ont 12 éléments. Le dernier cas est trivial; votre pivot est la médiane. Dans le cas contraire, appel nth_element soit sur le côté gauche ou le côté droit. La valeur exacte de la Nième dépend de combien de valeurs étaient plus grandes ou plus petites que le pivot. Par exemple, si la division est 12/14, vous avez besoin de plus petit élément que le pivot, de sorte Nième = 0, et si la division était 14/12, vous avez besoin le plus grand élément plus petit pivot, donc Nième = 13. Les pires cas sont 26/0 et 0/26, lorsque votre pivot était extrême, mais ceux qui ne se produisent que dans 2/27 tous les cas.

La troisième amélioration (ou la première, si vous devez utiliser C et ne pas nth_element) entièrement nth_element remplace. Vous avez encore le tableau des éléments 53, mais cette fois que vous remplissez directement à partir des valeurs Voxel (vous permet d'économiser une copie intermédiaire dans un float[27]). Le pivot dans cette première itération est juste Voxel [0] [0] [0]. Pour les itérations suivantes, vous utilisez une deuxième pré-alloué float[53] (Plus facile si les deux sont de la même taille) et copie flotte entre les deux. L'étape d'itération de base est toujours là: copier le pivot au milieu, sorte le reste à gauche et à droite. A la fin de chaque étape, vous saurez si la médiane est plus petit ou plus grand que le pivot actuel, de sorte que vous pouvez jeter les flotteurs plus grand ou plus petit que celui pivot. Par itération, ce qui élimine entre 1 et 12 éléments, avec une moyenne de 25% du reste.

La dernière itération, si vous avez besoin encore plus de vitesse, est basée sur l'observation que la plupart de vos voxels se chevauchent considérablement. Vous précalculer pour chaque tranche de 3x3x1 la valeur médiane. Ensuite, lorsque vous avez besoin d'un pivot initial pour votre cube 3x3x3 voxels, vous prenez la médiane du trois. Vous savez a priori qu'il ya 9 voxels plus petits et 9 voxels plus grand que celui médian des valeurs médianes (4 + 4 + 1). Ainsi, après la première étape de le pivotement, le pire des cas sont un 9/17 et une scission 17/9. , Vous auriez seulement besoin Donc, pour trouver le 4e ou 13e élément dans un flotteur [17], au lieu de 12 ou 14 dans un flotteur [26].


Contexte: L'idée de copier d'abord un pivot, puis le reste d'un flotteur [N] à un flotteur, en utilisant des pointeurs à gauche et à droite [2N-1] est que vous remplissez un flotteur [N] sous-tableau autour du pivot, avec tous les éléments plus petit que le pivot vers la gauche (d'indice inférieur) et plus élevé vers la droite (indice élevé). Maintenant, si vous voulez que l'élément Mth, vous pourriez vous retrouver chanceux et ont des éléments M-1 plus petit que le pivot, auquel cas le pivot est l'élément dont vous avez besoin. S'il y a plus de (M-1) éléments plus petits que le pivot, l'élément MTH d'entre eux, de sorte que vous pouvez jeter le pivot et quoi que ce soit plus grand que le pivot, et pour l'élément Critères de recherche Mth dans toutes les valeurs inférieures. S'il y a moins que (M-1) éléments plus petits que le pivot, vous êtes à la recherche d'une valeur supérieure à celle du pivot. Alors, vous défaussez le pivot et rien de plus petit que lui. Que le nombre d'éléments moins que le pivot, soit vers la gauche du pivot soit L. Dans la prochaine itération, vous voulez que le (ML-1) ième élément des flotteurs (NL-1) qui sont plus grandes que le pivot.

Ce type d'algorithme nth_element est assez efficace, car la plupart du travail est consacré à des flotteurs de copie entre deux petits tableaux, qui seront tous deux dans le cache, et parce que votre état est la plupart du temps représenté par 3 pointeurs (pointeur source, pointeur de destination gauche , pointeur de destination à droite).

Pour afficher le code de base:

float in[27], out[53];
float pivot = out[26] = in[0];     // pivot
float* left = out+25, right = out+27
for(int i = 1; i != 27; ++1)
if((in[i]<pivot)) *left-- = in[i] else *right++ = in[i];
// Post-condition: The range (left+1, right) is initialized.
// There are 25-(left-out) floats <pivot and (right-out)-27 floats >pivot
6
MSalters

Un réseau de tri généré à l'aide de l'algorithme Bose-Nelson trouvera la médiane directement sans boucles/récursives en utilisant 173 comparaisons. Si vous avez la possibilité de faire des comparaisons parallèlement, telles que l'utilisation d'instructions de vecteur-arithmétique, vous pourrez peut-être regrouper les comparaisons en moins de 28 opérations parallèles.

Si vous êtes sûr que les flotteurs sont normalisés et non (QS) Nan, vous pouvez utiliser des opérations entière pour comparer les flotteurs IEEE-754 qui peuvent effectuer plus favorablement sur certains CPU.

Une conversion directe de ce réseau de tri en C (GCC 4.2) donne un pire cas de 388 cycles d'horloge sur mon noyau i7.

Tri des réseaux

4
matja

Je suppose que votre meilleur pari est de prendre un algorithme de tri existant et d'essayer de déterminer si vous pouvez l'adapter afin que l'ensemble ne soit pas nécessaire d'être complètement trié. Pour déterminer la médiane, vous avez besoin de la moitié de la moitié des valeurs triées, soit la moitié inférieure ou supérieure suffirait:

original:              | 5 | 1 | 9 | 3 | 3 |
sorted:                | 1 | 3 | 3 | 5 | 9 |
lower half sorted:     | 1 | 3 | 3 | 9 | 5 |
higher half sorted:    | 3 | 1 | 3 | 5 | 9 |

L'autre moitié serait un seau de valeurs non formées qui partagent simplement la propriété d'être plus grande/plus petite ou égale à la plus grande valeur triée.

Mais je n'ai pas d'algorithme prêt pour cela, c'est juste une idée de la façon dont vous pourriez prendre une coupe courte dans votre tri.

3
unwesen

Le nouveau livre de Alex Stepanov - éléments de programmation discute à une certaine longueur sur la recherche de statistiques de commande à l'aide du nombre minimal de comparaisons moyennes tout en minimisant les frais généraux d'exécution. Malheureusement, une quantité importante de code est nécessaire pour calculer la médiane de 5 éléments, et même il donne un projet de recherche d'une solution alternative qui utilise une fraction d'une comparaison de moins en moyenne, donc je ne rêverais donc pas à étendre que cadre pour trouver la médiane de 27 éléments. Et le livre ne sera même pas disponible avant le 15 juin 2009. Le point est que, comme il s'agit d'un problème de taille fixe, il existe une méthode de comparaison directe qui est prouvante optimale.

En outre, il y a le fait que cet algorithme ne soit pas exécuté une fois isolément, mais plusieurs fois, et entre la plupart des points seulement 9 des 27 valeurs changeront. Cela signifie en théorie une partie du travail est déjà fait. Cependant, je n'ai entendu parler d'algorithmes de filtrage médian dans le traitement de l'image qui profitent de ce fait.

2
Mark Ruzon

+1 Pour tout le monde qui a mentionné NTH_Element, mais ce type de code est le lieu où l'algorithme écrit à la main est meilleur que STL, car vous souhaitez générer le code le plus efficace pour qu'un compilateur exécuté sur l'un des processeurs avec un ensemble de données spécifique. Par exemple, pour certains combinaisons CPU/compilateur STD :: Swap (INT, INT) peut-être plus lent que l'échange écrit à la main en utilisant XOR (Avant de répondre, je sais que cela est probablement vrai il y a 20 ans mais pas) . Parfois, la performance est gagnée à la main écrit le code d'assemblage spécifique à votre CPU. Si vous envisagez de tirer parti des processeurs de flux de GPU, vous devrez peut-être concevoir votre algorithme en conséquence.

Vous avez mentionné en utilisant 2 tas et gardez une trace de la médiane lorsque vous insérez. C'est ce que j'ai fait il y a quelque temps dans un projet. J'ai changé le tableau surplace et utilisé un seul tas. Je ne pouvais penser à un algorithme plus rapide, mais je voudrais vous mettre en garde sur l'utilisation de la mémoire, spécifiquement la mémoire cache de la CPU. Vous voulez être prudent avec l'accès à la mémoire. CPU Cache est échangé dans et sortie par page. Vous souhaitez donc que votre algorithme touche la mémoire proche de la MLAI de CPU Cache Mlle.

2
Shing Yip

Lorsque vous avez indiqué un million de valeurs différentes à partir de laquelle vous avez besoin de la médiane. Est-il possible de baser votre médiane sur un sous-ensemble de ces millions, disons 10%. De sorte que la médiane soit proche de l'élément n-ème qui divise les valeurs en 2 sous-ensembles égaux (ou presque égaux)? Car, pour trouver la médiane, vous aurez besoin de moins que O (n) - (dans ce cas O(1/10n) et se rapproche par la présente du tri optimal avec QuicksTort dans O (Nlogn )?

1
barre

Vous voudrez peut-être jeter un coup d'œil à l'exercice de Knuth 5.3.3.13. Il décrit un algorithme dû à Floyd qui trouve la médiane des n éléments utilisant (3/2) N + O (n ^ (2/3) N + O (n ^ (2/3) Log n) Comparaisons et la constante cachée dans le O (·) ne semble pas être trop gros dans la pratique.

0
anonymous

Si vous voulez voir des algorithmes, recherchez les livres de Donald E. Knuth.

Ps. Si vous pensez avoir inventé quelque chose de mieux, vous devriez être capable de montrer que la complexité est similaire ou meilleure à la complexité des algorithmes connus. Qui pour les variations basées sur le godet et le radix est O(n) d'autre part, tri rapide est uniquement O (N.log (n)). Une méthode de 20% plus rapide est toujours O(n.log(n)) jusqu'à ce que vous puissiez montrer l'algorithme :-)

0
Martin York

Je parie que vous pouvez les calculer pour des coûts zéro - dans un fil séparé lors du chargement du disque (ou à cependant généré).

Ce que je dis vraiment, c'est que "la vitesse" ne viendra pas de bit Twiddling car 27 valeurs ne suffisent pas pour que la grosse notation soit un facteur réel.

0
Jimmy J