web-dev-qa-db-fra.com

Quelle est la meilleure façon de générer des bools aléatoires en termes de performances?

J'ai besoin de générer des valeurs booléennes aléatoires sur un chemin critique pour les performances.

Le code que j'ai écrit pour cela est

std::random_device   rd;
std::uniform_int_distribution<> randomizer(0, 1);
const int val randomizer(std::mt19937(rd()));
const bool isDirectionChanged = static_cast<bool>(val);

Mais ne pensez pas que c'est la meilleure façon de le faire car je n'aime pas faire static_cast<bool>.

Sur le web, j'ai trouvé quelques solutions supplémentaires

1.std::bernoulli_distribution

2.bool randbool = Rand() & 1; N'oubliez pas d'appeler srand() au début.

42
T M

Aux fins de la performance, à un prix de moins "aléatoire" que par ex. std::mt19937_64, vous pouvez utiliser Xorshift + pour générer des nombres 64 bits, puis utiliser les bits de ces nombres comme des booléens pseudo-aléatoires.

Citant Wikipédia:

Ce générateur est l'un des générateurs les plus rapides passant BigCrush

Détails: http://xorshift.di.unimi.it/ . Un tableau de comparaison au milieu de la page indique que mt19937_64 est 2 fois plus lent et systématique.

Voici un exemple de code (le vrai code devrait l'envelopper dans une classe):

#include <cstdint>
#include <random>
using namespace std;

random_device rd;
/* The state must be seeded so that it is not everywhere zero. */
uint64_t s[2] = { (uint64_t(rd()) << 32) ^ (rd()),
    (uint64_t(rd()) << 32) ^ (rd()) };
uint64_t curRand;
uint8_t bit = 63;

uint64_t xorshift128plus(void) {
    uint64_t x = s[0];
    uint64_t const y = s[1];
    s[0] = y;
    x ^= x << 23; // a
    s[1] = x ^ y ^ (x >> 17) ^ (y >> 26); // b, c
    return s[1] + y;
}

bool randBool()
{
    if(bit >= 63)
    {
        curRand = xorshift128plus();
        bit = 0;
        return curRand & 1;
    }
    else
    {
        bit++;
        return curRand & (1<<bit);
    }
}
39
Serge Rogatch

Quelques repères rapides ( code ):

   647921509 RandomizerXorshiftPlus
   821202158 BoolGenerator2 (reusing the same buffer)
  1065582517 modified Randomizer
  1130958451 BoolGenerator2 (creating a new buffer as needed)
  1140139042 xorshift128plus
  2738780431 xorshift1024star
  4629217068 std::mt19937
  6613608092 Rand()
  8606805191 std::bernoulli_distribution
 11454538279 BoolGenerator
 19288820587 std::uniform_int_distribution

Pour ceux qui veulent du code prêt à l'emploi, je vous présente XorShift128PlusBitShifterPseudoRandomBooleanGenerator, une version modifiée de RandomizerXorshiftPlus à partir du lien ci-dessus. Sur ma machine, elle est à peu près aussi rapide que la solution de @ SergeRogatch, mais toujours environ 10 à 20% plus rapide lorsque le nombre de boucles est élevé (≳100 000), et jusqu'à ~ 30% plus lent avec des nombres de boucles plus petits.

class XorShift128PlusBitShifterPseudoRandomBooleanGenerator {
public:
  bool randBool() {
    if (counter == 0) {
      counter = sizeof(GeneratorType::result_type) * CHAR_BIT;
      random_integer = generator();
    }
    return (random_integer >> --counter) & 1;
  }

private:
  class XorShift128Plus {
  public:
    using result_type = uint64_t;

    XorShift128Plus() {
      std::random_device rd;
      state[0] = rd();
      state[1] = rd();
    }

    result_type operator()() {
      auto x = state[0];
      auto y = state[1];
      state[0] = y;
      x ^= x << 23;
      state[1] = x ^ y ^ (x >> 17) ^ (y >> 26);
      return state[1] + y;
    }

  private:
    result_type state[2];
  };

  using GeneratorType = XorShift128Plus;

  GeneratorType generator;
  GeneratorType::result_type random_integer;
  int counter = 0;
};
18
emlai

Une façon serait de simplement générer un unsigned long long pour 64 appels aléatoires, comme indiqué dans les commentaires. Un exemple:

#include <random>
class Randomizer
{
public:
    Randomizer() : m_Rand(0), counter(0), randomizer(0, std::numeric_limits<unsigned long long>::max()) {}

    bool RandomBool()
    {
        if (!counter)
        {
            m_Rand = randomizer(std::mt19937(rd()));
            counter = sizeof(unsigned long long) * 8;

        }
        return (m_Rand >> --counter) & 1;
    }
private:
    std::random_device  rd;
    std::uniform_int_distribution<unsigned long long> randomizer;
    unsigned long long m_Rand;
    int counter;
};
10
Sombrero Chicken

Je préremplirais un tampon (assez long) (circulaire) de valeurs aléatoires 64 bits, puis prendrais très rapidement un bit à la fois lorsque vous avez besoin d'une valeur aléatoire booléenne

#include <stdint.h>

class BoolGenerator {
  private:
  const int BUFFER_SIZE = 65536;
  uint64_t randomBuffer[BUFFER_SIZE];
  uint64_t mask;
  int counter;

  void advanceCounter {
    counter++;
    if (counter == BUFFER_SIZE) {
        counter = 0;
    }
  }

  public:
  BoolGenerator() {
    //HERE FILL YOUR BUFFER WITH A RANDOM GENERATOR
    mask = 1;
    counter = 0;
  }

  bool generate() {
    mask <<= 1;
    if (!mask) { //After 64 shifts the mask becomes zero
        mask = 1;//reset mask
        advanceCounter();//get the next value in the buffer
    }
    return randomBuffer[counter] & mask;
  }
}

Bien sûr, la classe peut être généralisée à la taille du tampon, au générateur aléatoire, au type de base (ne doit pas nécessairement être uint64_t), etc.


Accès au tampon une seule fois tous les 64 appels:

#include <stdint.h> //...and much more

class BoolGenerator {
  private:
  static const int BUFFER_SIZE = 65536;
  uint64_t randomBuffer[BUFFER_SIZE];
  uint64_t currValue;
  int bufferCounter;
  int bitCounter;

  void advanceBufferCounter() {
    bufferCounter++;
    if (bufferCounter == BUFFER_SIZE) {
        bufferCounter = 0;
    }
  }

  void getNextValue() {
      currValue = randomBuffer[bufferCounter];
      bitCounter = sizeof(uint64_t) * 8;
      advanceBufferCounter();
  }

  //HERE FILL YOUR BUFFER WITH A RANDOM GENERATOR
  void initializeBuffer() {
  //Anything will do, taken from here: http://stackoverflow.com/a/19728404/2436175
      std::random_device rd;
      std::mt19937 rng(rd());
      std::uniform_int_distribution<uint64_t> uni(0,std::numeric_limits<uint64_t>::max());
      for (int i = 0; i < BUFFER_SIZE; i++ ) {
          randomBuffer[i] = uni(rng);
      }
  }

  public:
  BoolGenerator() {
      initializeBuffer();
      bufferCounter = 0;
      getNextValue();
  }

  bool generate() {
      if (!bitCounter) {
           getNextValue();
      }
      //A variation of other methods seen around
      bitCounter--;
      bool retVal = currValue & 0x01;
      currValue >>= 1;
      return retVal;
  }
};
4
Antonio

Sauf si vous avez d'autres contraintes sur le caractère aléatoire dont vous avez besoin, le moyen le plus rapide de générer un booléen aléatoire est:

bool RandomBool() { return false; }

Pour être plus précis, il existe des milliers de façons de générer des nombres booléens aléatoires, tous satisfaisant des contraintes différentes, et bon nombre d'entre eux ne fournissent pas de "nombres véritablement" aléatoires (qui inclut toutes les autres réponses jusqu'à présent). Le mot "aléatoire" seul ne dit à personne les propriétés dont vous avez vraiment besoin.

3
Peter

Si la performance est votre seul critère, alors la réponse est:

bool get_random()
{
    return true; // chosen by fair coin flip.
                 // guaranteed to be random.
}

Malheureusement, l'entropie de ce nombre aléatoire est nulle, mais les performances sont assez rapides.

Étant donné que je soupçonne que ce générateur de nombres aléatoires ne vous est pas très utile, vous devrez quantifier à quel point vous voulez que vos booléens soient aléatoires . Que diriez-vous d'une durée de cycle de 2048? Un million? 2 ^ 19937-1? Jusqu'à la fin de l'univers?

Je soupçonne que, puisque vous avez explicitement déclaré que la performance est votre plus grande préoccupation, alors un bon générateur congruentiel linéaire à l'ancienne pourrait être "assez bon". Basé sur cet article , je suppose que la période de ce générateur est d'environ 32 * ((2 ^ 31) -5), soit environ 68 billions d'itérations. Si ce n'est pas "assez bon", vous pouvez ajouter n'importe quel générateur compatible C++ 11 que vous aimez au lieu de minstd_Rand.

Pour un crédit supplémentaire et une petite performance, modifiez le code ci-dessous pour utiliser algorithme de pièce biaisée pour supprimer le biais dans le générateur.

#include <iostream>
#include <random>

bool get_random()
{
    typedef std::minstd_Rand generator_type;
    typedef generator_type::result_type result_type;

    static generator_type generator;
    static unsigned int bits_remaining = 0;
    static result_type random_bits;

    if ( bits_remaining == 0 )
    {
        random_bits = generator();
        bits_remaining = sizeof( result_type ) * CHAR_BIT - 1;
    }

    return ( ( random_bits & ( 1 << bits_remaining-- ) ) != 0 );
}

int main()
{
    for ( unsigned int i = 0; i < 1000; i++ )
    {
        std::cout << " Choice " << i << ": ";
        if ( get_random() )
            std::cout << "true";
        else
            std::cout << "false";

        std::cout << std::endl;
    }
}
1
johnwbyrd

je pense que la meilleure façon est d'utiliser un tableau aléatoire précalculé:

uint8_t g_Rand[UINT16_MAX];
bool InitRand()
{
    for (size_t i = 0, n = UINT16_MAX; i < n; ++i)
        g_Rand[i] = ::Rand() & 1;
    return true;
}
bool g_inited = InitRand();
inline const uint8_t * Rand()
{
    return g_Rand + (::Rand()&INT16_MAX);
}

Il utilise pour remplir un tableau dst [taille]:

const size_t size = 10000;
bool dst[size];
for (size_t i = 0; i < size; i += INT16_MAX)
     memcpy(dst + i, Rand(), std::min<size_t>(INT16_MAX, size - col));

Bien sûr, vous pouvez initialiser un tableau pré-calculé en utilisant une autre fonction aléatoire.

0
ErmIg

Apparemment, je dois ajouter une autre réponse. Je viens de comprendre qu'à partir de Ivy Bridge architecture Intel ajoutée RdRand instruction CPU et AMD l'a ajouté plus tard en juin 2015. Donc, si vous ciblez un processeur suffisamment nouveau et ne cela ne vous dérange pas d'utiliser l'assemblage (en ligne), le moyen le plus rapide de générer des bools aléatoires devrait être d'appeler RdRand une instruction CPU pour obtenir un nombre aléatoire de 64 bits comme décrit ici = (faites défiler vers le milieu de la page pour des exemples de code) (à ce lien, il y a aussi un exemple de code pour vérifier le CPU actuel pour le support de l'instruction RdRand, et voir aussi Wikipedia pour une explication sur la façon de faire avec CPUID ), puis utilisez les bits de ce nombre pour les booléens comme décrit dans ma Xorshit + réponse basée .

0
Serge Rogatch

si les performances sont importantes, c'est peut-être une bonne idée de générer un nombre aléatoire de 32 bits et d'utiliser chaque bit séparé, quelque chose comme ceci:

bool getRandBool() {
    static uint32_t randomnumber;
    static int i=0;
    if (i==0) {
        randomnumber = <whatever your favorite randonnumbergenerator is>;
        i=32;
    }
    return (randomnumber & 1<<--i); 
 }

de cette façon, la génération n'affecte que tous les 32e appel

0
Tommylee2k