| July 24, 2011 3:00 PM PDT | |
Problème Acceler8 : cprime
GOOG_ICEANDMAGIC
22/05/2011
Introduction
Nous expliquons, par le présent, notre solution au problème cprime du concours acceler8. Vous allez remarqué que nous avons fait un effort de recherche pour chercher les implémentations les plus fiables et les plus rapides. Nous aurons peut dû faire nos propres implémentations, mais nous considérons qu'il ne faut pas réinventer la roue, et qu'il y a des gens qui accordent une importance capitale à ce problème. Autant exploiter leurs efforts.Le travail de recherche fait équivaut à un vrai effort d'implémentation. Du moins, à notre, sens!
Définitions
Soient:
: l'ensemble des nombres premiers.
: l'ensemble des séquences de
nombres premiers consécutifs. Nous appelons une telle séquence clique.
: une fonction de
dans
: une fonction de
dans
- Couple de cliques : un pair
de deux éléments de
.
.
est un entier désignant une borne inférieure.
est un entier désignant une borne supérieure.
Problématique
Étant quatre entiers, ![]()
Il est évident que le gros travail consiste à calculer les 'séquences' de nombres premiers entre
et
. Cela avait été confirmé par mes statistiques. Le reste du rapport sera divisé en deux parties, une conscarée à la recherche des nombres premiers, l'autre au calcul des cliques
-proches.
Nombres Premiers en Pratique
Mâme si notre effort de documentation n'est certainement pas à la hauteur des travaux faits, nous résumons ce dont nous étions capable de trouver et de comprendre.
Tests Probabilistes & Deterministes
Il est clair que, pour le moment, les tests probabilistes dominent les implémentations 'sérieuses'. À l'image de la très connue bibliothèque GMP. Ce genre de tests de primalité renvoie un booléen flou 1. En fonction d'une précision paramétrable, pour un entier donné, ce booléen peut avoir les valeurs suivantes:- 0 : l'entier est composée.
- 2 : l'entier est premier.
- 1 : l'entier est probablement premier.
La fiabilité de l'algorithme, et sa lourdeur, dépend essentiellement du degré de confiance qu'on cherche à atteindre. Cela est fait en calibrant le paramètre de précision.
Ce genre de tests peut âtre utilisé pour repérer, rapidement, les nombres premiers probables avant de faire un test, naif peut âtre, beaucoup plus rigoureux.
La littérature semble se congratuler pour l'invention d'un test de primalité, AKS en occurrence 2, le plus rapide (complexité polynomiale). Nous n'avons trouvé aucune implémentation sérieuse, et l'algorithme nous semble assez flou pour faire un effort d'implémentation.
La fenâtrisation : Wheel Factorization
l'Idée est d'éviter de balayer bâtement tous les nombres impair d'un espace de recherche, mais de considérer que les éléments qui sont suspectibles d'âtre premiers. Il s'agit de la fenâtrisation (wheel vectorization). John MOYER 3 nous explique que seuls les nombres suivants, étant un
, sont suspectibles d'âtres premiers :
![]()
Appelons le
, dans
, fenâtre de recherche.
Évaluer les décalages
(dans l'exemple: 1, 7, 11, ... etc) par rapport à
consiste à retenir ceux qui ne font pas sortir un facteur commun avec la valeur de la fenâtre
, et donc d'éviter le: 2, 3, 4, 5, 6, 8, 9 ... Du coup, faire
tests de primalités au lieu de
.
La largeur de la fenâtre peut âtre personnalisée, d'ailleurs, John MOYER 4 dans son implémentation choisit une largeur
. Ce qui lui permet de ne faire que
tests au lieu de
. Le choix de la largeur de fânetre est simple, le produit des premiers nombres premiers.
Les Cribles
Étant le coût très elevé des tests de primalités unitaires, il est plus adapté (dans notre cas), d'utiliser une méthode dite par .La Crible d'd'Ératosthène
Consiste à éliminer tous les nombres divisibles par l'un des nombres premiers plus petits que lui. Les inconvignients sont multiples car:- Considérer tous les nombres d'un intervalle, ce qui peut âtre très gourment en mémoire.
- Commencer par 2 quelque soit la borne inférieur de l'intervalle.
L'implémentation la plus aboutie de cette technique est PrimeSieve, écrite en C++, 5 de Kim WALISCH. Comparée à celle de MOYER, PrimeSieve est beaucoup plus rapide, et beaucoup plus simple à utiliser.
La Crible d'Atkin par Bernstein
Nous nous sommes pas aller plus loin dans les détails, mais la crible d'Atkin semble âtre une amélioration de la crible d'Ératosthène pour réduire sa consommation de mémoire et améliorer la convergence de l'algorithme.La seule implémentation que nous ayons trouvé est celle de l'inventeur de la méthode, BERNSTEIN, primegen 6 écrite en C.
PrimeSieve vs primegen
Tous nos tests ont montré quePrimeSieve est sensiblement plus rapide que primegen. Nous l'avons retenu dans le release officiel de la compétition.
Présentation de la Solution
Discussion
Notre première intuition nous a orienté vers une approche où l'on ne calcule qu'une seule fois les nombres premiers. Cela revient à estimer une borne supérieure pour les nombres premiers, dits utiles (qui seront utilisé par nos calculs), puis les stocker dans une structure.Cette dernière doit favoriser un accès direct et rapide aux contenu, doit être également scalable pour réagir d'une manière flexible au nombre grandissant de nombres premiers d'un espace trop grand. Nous avons pensé au arbres balancés et au STL map, mais nous nous sommes vite rendu compte que la structure n'est pas scalable et entraine un surcoût considérable.
Un bon compromis aurait être l'utilisation d'une table (STL vector) indexé par un arbre balancé (STL map). Verdict, même les tables dynamiques génèrent des exception liées à l'incapacité de stocker tous les nombres. Et puis, même les tables engendrent un surcoût assez considérable.
Nous nous sommes, finalement, restreints à ne précalculer que les nombres premiers qui appartiennent à l'espace de recherche donné par l'utilisateur, et puis calculer au besoin tous ceux qui ne le sont pas.
Borne liée à la capacité : BC
Il nous est demandé de représenter un nombre premier par un entier de 64bits. Étant donné la fonction| 2 | |
| 3 | 2097152 |
| 4 | 55108.9875 |
| 5 | 6208.37506 |
| 6 | 1448.15469 |
| 7 | 512 |
| 8 | 234.753035 |
| 9 | 128 |
| 10 | 78.7932425 |
L'ensemble des valeurs de
qui permettent un calcul qui ne provoquera pas un dépassement de capacité est :
.
étant la borne inférieure de l'espace de recherche.
Considérant le meilleur cas : i.e.
, cet ensemble est très restreint
.
Pour un
donné, calculer des nombres premiers pour une clique en vue d'un calcul par la fonction
provoquera un dépassement de capacité et donc des valeurs incorrectes.
![]()
ici
correspond à ce que l'utilisateur donne comme
argument au binaire.
Chercher les Nombres Premiers
Le pseudo-code de la recherche des nombres premiers comprisentre
et
est le suivant :
void chercher_premiers(l,u, premiers){
Initialiser_recherche(l);
p = suivant();
for(;;){
if( p > u)
return;
inserer(premiers, p);
p=suivant();
}
}
Il est, à noter, qu'il y a une dépendance de données inter-itérations forte sur la variable p. La seule issue de parallelisation, à notre sens, est de faire un blocking de la sorte :
void chercher_premiers_bloc(l,u, premiers){
// blocking
for(i=l; i + BLOCK_SIZE< u ; i+=BLOCK_SIZE)
chercher_premiers(i,i+BLOCK_SIZE-1,premiers);
// epilogue du blocking
chercher_premiers(i,i+BLOCK_SIZE-1,premiers);
}
Cette version maintient une dépendance sur premiers, mais elle n'est qu'une dépendance de sortie, donc, facile à enlever. La parallélisation de la boucle i est envisageable selon la rentabilité de cette transformation, qui dépend essentiellement de la valeur BLOCK_SIZE. Car nous nous payons les frais de :
u/BLOCK_SIZEinitialisations.- le double calcul de
u/BLOCK_SIZEnombres premiers.
Chercher les Cliques Proches
Une fois la liste des nombres premiers calculée, la recherche des cliques proches devient un calcul à coût dérisoire. Nos statistiques ont montré que cette partie ne consomme, dans tous les cas, que
void chercher_cliques_proches(premiers,k,alpha){
vecteur_circulaire c1, c2;
for(i=0; i < taille(premiers)-k+1; i++){
// remplir c1 par les k premiers elements en commençant \`a partir de i
remplir(c1,i,k,premiers);
// estimer une borne inf\'erieure pour les \'el\'ements de la clique c2
bi = estimer_debut_pour_c2(k, alpha, P(c1));
bs = estimer_fin_pour_c2(k, alpha, P(c1));
// calculer les nombres premiers compirs entre bi et bs
chercher_premiers(bi,bs, autres_premiers);
// remplir la table circulaire
remplir(c2,0,k,autres_premiers);
j=k;
while( S(c2) + alpha < P(c1) ){
if( abs( S(c2) - P(c1) ) <= alpha ){
afficher(c1);
afficher(c2);
afficher(abs( S(c2) - P(c1) ));
}
j++;
inserer(c2,autres_premiers[j]);
}
inserer(c1,premiers[i]);
}
}
Les boucles itc1, itc2 sont complètement parallèles.
Il est à noter qu'il y a un risque de recalculer plusiers fois les mêmes nombres premiers (dans autres_premiers). Mais cela est très dérisoire car:
- Pour le nombres petits, le calcul est assez rapide. Plus rapide même que le coût.
- Il y a une très faible probabilité de recalcul pour les nombres assez grands.
Cependant, cet algorithme peut être amélioré dans ce sens là. Quand nous aurons le temps !
Mise en Oeuvre du Parallélisme & Résultats
Parallélisation
La collecte des nombres premier est éxecutée en parallèle pour tout intervalle de longeure supérieure àLa recherche des cliques proches est lancée en parallèle pour les très grands nombres ou un intervalle de recherche relativement large.
Quelques Chiffres
Tous les tests ont été réalisés 100 fois. Les performances medianes ont été retenues. La machine utilisée est une 8-multicoeurs Intel Xéon 3Ghz avec 8Mo de chache. La MTL étant trop chargée pour l'utiliser.Pour un
,
à partir de 1 :
| bourne supérieure |
tout (us) | Threads |
| 84 | 1 | |
| 1378 | 1 | |
| 1457 | 1 | |
| 1098 | 1 | |
| 85192 | 1 | |
| 9318984 | 5 | |
| 8s90 | 7 |
D'autres tests ont été faits, malheureusement, nous ne connaissons pas ceux qui sont les plus pertinents. Nous nous contentons de précédents pour se comparer aux chiffres annoncés sur le forum.
Conclusion
Nous avons la conviction que notre travail, élaboré dans la précipitation due au manque du temps, peut être grandement amélioré en utilisant des tests de primalités unitaire couplés à un mécanisme de fenêtrisation (wheel factorization) au lieu des cribles.Mais cela ne garantit, malheureusement, pas la scalabilité de la solution. étant donné que même le test de primalité, prouvé le plus rapide (AKS), est polynomial. Ce qui n'arrange que peu notre problème.
Téléchargement
Footnotes
- ... flou1
- Voir : Logique Floue
- ... occurrence2
- M. Agrawal, N. Kayal, N. Saxena. Prime is in P
- ...Moyer 3
- J. Moyer, http://www.rsok.com/ jrm/printprimes.html.
- ...Moyer 4
- sieve2310 de John Moyer, ftp://ftp.rsok.com/pub/source/sieve2310.c
- ... C++,5
- PrimeSieve de Kim Walisch, http://code.google.com/p/primesieve/
- ...
primegen6 - primegen de Bernstein, http://cr.yp.to/primegen.html
Reportez-vous à notre Notice d'optimisation pour plus d'informations sur les choix et l'optimisation des performances dans les produits logiciels Intel.
Commentaires (4) 
| August 1, 2011 7:32 AM PDT
farcellier
| Excellent article. Merci pour le passage qui explique la fenêtrisation. C'est un procédé simple que nous avons raté. |
| August 1, 2011 1:40 PM PDT
goog_iceandmagic
|
Merci à vous deux, Pour être honnête, j'ai improvisé le terme 'fenêtrisation' pour traduire 'wheel factorisation' (qui se traduit en "factorisation en roues" que je n'aime pas trop)... Donc il ne faut pas prendre au sérieux cette termonologie. J'aurais pû utiliser "factorisation", mais j'avait craint une ambiguité avec "la factorisation en nombres premiers". merci, -- PS: Le "chercher/remplacer" est coupable ... dans l'article, tous les "â" sont à remplacer par des "ê"! et puis désolé pour les deux/trois fautes d'orthographe. |
| August 4, 2011 8:39 AM PDT
Anthony Charbonnier (Intel)
| Les fautes d'orthographes sont excusées ;) ! |
Trackbacks (0)
Réagir 
goog_iceandmagic
|


Anthony Charbonnier (Intel)
4,883