2017-07-02 3 views
2

j'ai le code suivant (the xorshift128+ code from Wikipedia modifié pour utiliser les types de vecteurs):Mon vectorisé xorshift + est pas très aléatoire

#include <immintrin.h> 
#include <climits> 

__v8si rand_si() { 
    static auto s0 = __v4du{4, 8, 15, 16}, 
     s1 = __v4du{23, 34, 42, 69}; 
    auto x = s0, y = s1; 
    s0 = y; 
    x ^= x << 23; 
    s1 = x^y^(x >> 17)^(y >> 26); 
    return (__v8si)(s1 + y); 
} 

#include <iostream> 
#include <iomanip> 
void foo() { 
    //Shuffle a bit. The result is much worse without this. 
    rand_si(); rand_si(); rand_si(); rand_si(); 
    auto val = rand_si(); 

    for (auto it = reinterpret_cast<int*>(&val); 
     it != reinterpret_cast<int*>(&val + 1); 
     ++it) 
     std::cout << std::hex << std::setfill('0') << std::setw(8) << *it << ' '; 
    std::cout << '\n'; 
} 

qui délivre en sortie

09e2a657 000b8020 1504cc3b 00110040 1360ff2b 00150078 2a9998b7 00228080 

tout autre nombre est très faible, et aucun n'a le premier bit défini. D'autre part, en utilisant xorshift * au lieu:

__v8si rand_si() { 
    static auto x = __v4du{4, 8, 15, 16}; 
    x ^= x >> 12; 
    x ^= x << 25; 
    x ^= x >> 27; 
    return x * (__v4du)_mm256_set1_epi64x(0x2545F4914F6CDD1D); 
} 

je reçois la meilleure sortie

0889632e a938b990 1e8b2f79 832e26bd 11280868 2a22d676 275ca4b8 10954ef9 

Mais selon Wikipedia, xorshift + est une bonne PRNG, et produit une meilleure pseudo-aléatoire que xorshift * . Alors, ai-je un bug dans mon code RNG, ou est-ce que je l'utilise mal?

+2

Vous construisez avec 'clang', non? [gcc dit] (https://godbolt.org/g/qLUFuz) * : 11: 17: note: -les conversions vectorielles -flax pour permettre les conversions entre vecteurs avec différents types d'éléments ou nombres de sous-parties * pour 'return s1 + y; ' –

+0

@PeterCordes, Oui, c'est correct. – Dan

+0

Pourquoi utilisez-vous '__v8si' et' __v4du' à la place des types standard définis par Intel? –

Répondre

3

Je pense que vous ne devriez pas juger un générateur aléatoire en regardant 8 nombres qu'il a générés. En outre, les générateurs ont généralement besoin d'un bon ensemencement (votre semis peut être considéré comme mauvais - vos graines commencent avec presque tous les bits de zéros) Appeler rand_si() juste quelques fois ne suffit pas pour que les bits "se propagent").

Donc, je vous recommande d'utiliser un bon ensemencement (par exemple, une solution simple est d'appeler rand_si() encore plus de fois).

xorshift* ressemblent à se comporter mieux en raison de la multiplication finale, donc il n'a pas facilement repéré un mauvais comportement en raison de l'ensemencement inadéquat.

Astuce: comparez les nombres que votre code génère avec l'implémentation d'origine. De cette façon, vous pouvez être sûr que votre implémentation est correcte.

2

La réponse de geza était exactement exacte, l'ensemencement était le coupable. Il a beaucoup mieux fonctionné pour l'amorcer en utilisant un PRNG 64 bits standard:

void seed(uint64_t s) { 
    std::mt19937_64 e(s); 
    s0 = __v4du{e(), e(), e(), e()}; 
    s1 = __v4du{e(), e(), e(), e()}; 
} 
+0

Juste un petit coup de pinceau ici: l'ordre des appels 'e()' n'est pas spécifié dans s0 et s1 (le compilateur est libre de décider de l'ordre des appels 'e()' pour le constructeur). Donc, si vous avez besoin d'un résultat reproductible parmi différentes versions de compilateurs/compilateurs, vous devez d'abord mettre les résultats de 'e()' dans les variables. – geza

+1

@geza N'est-ce pas l'ordre [défini dans une liste init-braced] (https://stackoverflow.com/q/14060264/603688)? – Dan

+0

oups, vous avez absolument raison!Je suis désolé pour la mauvaise information. La norme fait une exception pour ce cas. Intéressant... – geza