Normalement, un générateur de nombre aléatoire renvoie un flux de bits pour laquelle la probabilité d'observer un 0 ou un 1 dans chaque position est égale (c'est à dire 50%). Appelons cela un impartiale PRNG.
J'ai besoin de générer une chaîne de caractère pseudo-aléatoire de bits avec la propriété suivante: la probabilité de voir un 1 dans chaque position p (la probabilité de voir un 0 est 1-p). Le paramètre p est un nombre réel entre 0 et 1; à mon problème, il arrive qu'il dispose d'une résolution de 0,5%, c'est à dire qu'il peut prendre les valeurs 0%, 0.5%, 1%, 1.5%, ..., 99.5%, 100%.
Notez que p est une probabilité et non une fraction exacte. Le nombre effectif de bits mis à 1 dans un flux de n bits doivent suivre la loi binomiale B(n, p).
Il y a une méthode naïve qui peut utiliser un estimateur sans biais GÉNÉRATEUR pour générer la valeur de chaque bit (pseudo-code):
generate_biased_stream(n, p):
result = []
for i in 1 to n:
if random_uniform(0, 1) < p:
result.append(1)
else:
result.append(0)
return result
Une telle mise en œuvre est beaucoup plus lent que celui de la génération d'une impartiale des flux, puisqu'il utilise le générateur de nombre aléatoire de fonction une fois par chaque bit; tandis que l'impartialité du générateur de flux appelle une fois par taille de mot (par exemple, il peut générer de 32 ou 64 bits aléatoires, avec un seul appel).
Je veux une mise en œuvre plus rapide, même s'il sacrifices aléatoire légèrement. Une idée qui vient à l'esprit est de précalculer une table de recherche: pour chacun des 200 valeurs possibles de p, calculer C 8-bits des valeurs à l'aide du ralentissement de l'algorithme et de les enregistrer dans une table. Ensuite, l'algorithme rapide serait tout simplement choisir l'un de ces au hasard afin de générer 8 biaisée bits.
Un dos de l'enveloppe calcul pour voir combien de mémoire est nécessaire: C doit être d'au moins 256 (au nombre de 8 bits des valeurs), probablement plus pour éviter les effets d'échantillonnage; disons 1024. Peut-être le nombre varie en fonction de p, mais nous allons garder les choses simples et dire que la moyenne est de 1024. Depuis il y a 200 valeurs de p => total de l'utilisation de la mémoire est de 200 KO. Ce n'est pas mauvais, et peut tenir dans le cache L2 de 256 KO). J'ai encore besoin de l'évaluer pour voir si il y a d'échantillonnage effets introduire des biais, dans ce cas C devra être augmenté.
Une carence de cette solution est qu'elle peut générer, à seulement 8 bits à la fois, de même qu'avec beaucoup de travail, alors que l'impartialité du GÉNÉRATEUR peut générer 64 à la fois avec juste un peu d'arithmétique instructions.
Je voudrais savoir si il existe une méthode plus rapide, basé sur les opérations sur les bits au lieu de tables de recherche. Par exemple, la modification de la génération de nombre aléatoire directement le code à introduire un biais pour chaque bit. Cela permettrait d'obtenir la même performance que l'impartialité du PRNG.
Edit 5 Mars
Merci à vous tous pour vos suggestions, j'ai eu beaucoup d'idées intéressantes et de suggestions. Voici le top:
- Changer le problème, les exigences de sorte que p a une résolution de 1/256 au lieu de 1/200. Ceci permet d'utiliser des bits de façon plus efficace, et vous offre également plus de possibilités pour l'optimisation. Je pense que je peux faire ce changement.
- Utiliser le codage arithmétique à consommer efficacement bits à partir de l'impartialité du générateur. Avec la modification ci-dessus de résolution, cela devient beaucoup plus facile.
- Quelques personnes ont suggéré que PRNGs sont très rapides, donc en utilisant le codage arithmétique pourrait en fait rendre le code plus lent en raison de l'introduction de frais généraux. Au lieu de cela je doit toujours consommer le pire des cas, le nombre de bits et d'optimiser le code. Voir les indices de référence ci-dessous.
- @rici a suggéré d'utiliser SIMD. C'est une belle idée, qui ne fonctionne que si nous avons toujours consommer un nombre fixe de bits.
Référence (sans décodage arithmétique)
Remarque: comme beaucoup d'entre vous l'ont suggéré, j'ai changé la résolution de 1/200 à 1/256.
J'ai écrit plusieurs implémentations de la méthode naïve qui prend simplement 8 random impartiale bits et génère 1 peu biaisée:
- Sans SIMD
- Avec SIMD à l'aide de la Agner de la Brume vectorclass de la bibliothèque, comme suggéré par @rici
- Avec SIMD à l'aide de intrinsèques
J'utilise deux impartiale pseudo-générateurs de nombres aléatoires:
- xorshift128plus
- Ranvec1 (Mersenne Twister-like) à partir de Agner de la Brume de la bibliothèque.
J'ai aussi mesurer la vitesse de la neutralité PRNG à des fins de comparaison. Voici les résultats:
RNG: Ranvec1(Mersenne Twister for Graphics Processors + Multiply with Carry)
Method: Unbiased with 1/1 efficiency, SIMD=vectorclass (incorrect, baseline)
Gbps/s: 16.081 16.125 16.093 [Gb/s]
Number of ones: 536,875,204 536,875,204 536,875,204
Theoretical : 104,857,600
Method: Biased with 1/8 efficiency
Gbps/s: 0.778 0.783 0.812 [Gb/s]
Number of ones: 104,867,269 104,867,269 104,867,269
Theoretical : 104,857,600
Method: Biased with 1/8 efficiency, SIMD=vectorclass
Gbps/s: 2.176 2.184 2.145 [Gb/s]
Number of ones: 104,859,067 104,859,067 104,859,067
Theoretical : 104,857,600
Method: Biased with 1/8 efficiency, SIMD=intrinsics
Gbps/s: 2.129 2.151 2.183 [Gb/s]
Number of ones: 104,859,067 104,859,067 104,859,067
Theoretical : 104,857,600
SIMD augmente les performances par un facteur 3 par rapport à la méthode scalaire. Il est 8 fois plus lent que la neutralité du générateur, comme prévu.
La manière la plus rapide biaisée générateur atteint 2,1 Go/s.
RNG: xorshift128plus
Method: Unbiased with 1/1 efficiency (incorrect, baseline)
Gbps/s: 18.300 21.486 21.483 [Gb/s]
Number of ones: 536,867,655 536,867,655 536,867,655
Theoretical : 104,857,600
Method: Unbiased with 1/1 efficiency, SIMD=vectorclass (incorrect, baseline)
Gbps/s: 22.660 22.661 24.662 [Gb/s]
Number of ones: 536,867,655 536,867,655 536,867,655
Theoretical : 104,857,600
Method: Biased with 1/8 efficiency
Gbps/s: 1.065 1.102 1.078 [Gb/s]
Number of ones: 104,868,930 104,868,930 104,868,930
Theoretical : 104,857,600
Method: Biased with 1/8 efficiency, SIMD=vectorclass
Gbps/s: 4.972 4.971 4.970 [Gb/s]
Number of ones: 104,869,407 104,869,407 104,869,407
Theoretical : 104,857,600
Method: Biased with 1/8 efficiency, SIMD=intrinsics
Gbps/s: 4.955 4.971 4.971 [Gb/s]
Number of ones: 104,869,407 104,869,407 104,869,407
Theoretical : 104,857,600
Pour xorshift, SIMD augmente les performances par un facteur 5 par rapport à la méthode scalaire. Il est 4 fois plus lent que la neutralité du générateur. Notez que c'est un scalaire de la mise en œuvre de xorshift.
La manière la plus rapide biaisée générateur atteint 4.9 Go/s.
RNG: xorshift128plus_avx2
Method: Unbiased with 1/1 efficiency (incorrect, baseline)
Gbps/s: 18.754 21.494 21.878 [Gb/s]
Number of ones: 536,867,655 536,867,655 536,867,655
Theoretical : 104,857,600
Method: Unbiased with 1/1 efficiency, SIMD=vectorclass (incorrect, baseline)
Gbps/s: 54.126 54.071 54.145 [Gb/s]
Number of ones: 536,874,540 536,880,718 536,891,316
Theoretical : 104,857,600
Method: Biased with 1/8 efficiency
Gbps/s: 1.093 1.103 1.063 [Gb/s]
Number of ones: 104,868,930 104,868,930 104,868,930
Theoretical : 104,857,600
Method: Biased with 1/8 efficiency, SIMD=vectorclass
Gbps/s: 19.567 19.578 19.555 [Gb/s]
Number of ones: 104,836,115 104,846,215 104,835,129
Theoretical : 104,857,600
Method: Biased with 1/8 efficiency, SIMD=intrinsics
Gbps/s: 19.551 19.589 19.557 [Gb/s]
Number of ones: 104,831,396 104,837,429 104,851,100
Theoretical : 104,857,600
Cette implémentation utilise AVX2 pour exécuter 4 impartiale xorshift générateurs en parallèle.
La manière la plus rapide biaisée générateur atteint 19.5 Go/s.
Des repères pour l'arithmétique de décodage
De simples tests montrent que l'arithmétique de décodage de code est le goulot d'étranglement, pas le PRNG. Donc je ne suis que l'analyse comparative la plus chère PRNG.
RNG: Ranvec1(Mersenne Twister for Graphics Processors + Multiply with Carry)
Method: Arithmetic decoding (floating point)
Gbps/s: 0.068 0.068 0.069 [Gb/s]
Number of ones: 10,235,580 10,235,580 10,235,580
Theoretical : 10,240,000
Method: Arithmetic decoding (fixed point)
Gbps/s: 0.263 0.263 0.263 [Gb/s]
Number of ones: 10,239,367 10,239,367 10,239,367
Theoretical : 10,240,000
Method: Unbiased with 1/1 efficiency (incorrect, baseline)
Gbps/s: 12.687 12.686 12.684 [Gb/s]
Number of ones: 536,875,204 536,875,204 536,875,204
Theoretical : 104,857,600
Method: Unbiased with 1/1 efficiency, SIMD=vectorclass (incorrect, baseline)
Gbps/s: 14.536 14.536 14.536 [Gb/s]
Number of ones: 536,875,204 536,875,204 536,875,204
Theoretical : 104,857,600
Method: Biased with 1/8 efficiency
Gbps/s: 0.754 0.754 0.754 [Gb/s]
Number of ones: 104,867,269 104,867,269 104,867,269
Theoretical : 104,857,600
Method: Biased with 1/8 efficiency, SIMD=vectorclass
Gbps/s: 2.094 2.095 2.094 [Gb/s]
Number of ones: 104,859,067 104,859,067 104,859,067
Theoretical : 104,857,600
Method: Biased with 1/8 efficiency, SIMD=intrinsics
Gbps/s: 2.094 2.094 2.095 [Gb/s]
Number of ones: 104,859,067 104,859,067 104,859,067
Theoretical : 104,857,600
La simple méthode du point fixe réalise 0.25 Go/s, tandis que les naïfs scalaire méthode est 3x plus rapide, et les naïfs SIMD méthode est 8x plus rapide. Il y a peut-être des moyens pour optimiser et/ou de paralléliser le calcul méthode de décodage de l'avant, mais en raison de sa complexité, j'ai décidé d'arrêter ici et de choisir le naïf SIMD mise en œuvre.
Merci à vous tous pour l'aide.