在性能方面,生成随机布尔值的最佳方法是啥?

Posted

技术标签:

【中文标题】在性能方面,生成随机布尔值的最佳方法是啥?【英文标题】:What is performance-wise the best way to generate random bools?在性能方面,生成随机布尔值的最佳方法是什么? 【发布时间】:2016-05-23 08:39:23 【问题描述】:

我需要在性能关键路径上生成随机布尔值。

我为此编写的代码是

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);

但不要认为这是最好的方法,因为我不喜欢static_cast&lt;bool&gt;

在网上我找到了更多的解决方案

1.std::bernoulli_distribution

2.bool randbool = rand() &amp; 1;开头记得打srand()

【问题讨论】:

std::bernoulli_distribution 从我的经验来看很慢。最好的方法是生成unsigned long long(对于x64)并将其位用作布尔值。 你需要多少“随机性”?毕竟,您可以只声明未初始化的 int 并返回它的第一位。该值将是“随机的”,但分布未知。 @JakubZaverka:这是 undefined 行为 - 不能保证您的程序会正常工作。 xkcd.com/221 如果它对性能至关重要,那么您可能希望完全避免使用bool。如果你有很多信息,这是一种非常稀疏的存储信息的方式,如果它们是随机的,那么它们可能会强调分支预测。这取决于您的代码,但 PRNG 不太可能成为瓶颈。 【参考方案1】:

出于性能的目的,以比例如更少的“随机性”为代价。 std::mt19937_64,您可以使用 Xorshift+ 生成 64 位数字,然后将这些数字的位用作伪随机布尔值。

引用***:

此生成器是通过 BigCrush 的最快生成器之一

详情:http://xorshift.di.unimi.it/。页面中间有一个对比表,显示mt19937_64慢了2倍,是系统的。

下面是示例代码(真正的代码应该把它封装在一个类中):

#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);
    

【讨论】:

没有加号的 XorShift 几乎可以通过所有测试,并且应该快两倍多一点。这是一个额外的可能选择。 XorShift(+) 远未得到充分利用。基本上应该是所有平台上的标准生成器。不知道为什么超重量级的梅森捻线机这么受青睐。 @Serge Rogatch 使用位掩码? @usr 因为大多数程序员对伪随机数的生成知之甚少。 您可能应该采用 Jamie D 实现并替换为您的随机生成器 @usr: Mersenne Twister 几乎不是“超级重量级”。它更受欢迎的主要原因是因为 Xorshift+ 比 MT 更新得多。 Xorshift+ 于 2014 年首次发布,MT 自 1997 年以来一直存在。【参考方案2】:

一些快速基准测试 (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

对于那些想要即用型代码的人,我提供了XorShift128PlusBitShifterPseudoRandomBooleanGenerator,这是上面链接中RandomizerXorshiftPlus 的调整版本。在我的机器上,它与 @SergeRogatch 的解决方案一样快,但在循环计数较高 (≳100,000) 时始终快约 10-20%,而在循环计数较小时则慢约 30%。

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;
;

【讨论】:

不错。剩下的问题是所得分布的属性是否可以接受。 我认为 BoolGenerator 的构造函数,带有随机数生成,应该从测量中取出(这是一个预计算)。顺便说一句,我稍后会看到 BoolGenerator 是否可以通过将缓冲区当前指向的元素的副本保存在单独的变量中来进一步优化。它基本上会变成“修改后的随机器”,但使用缓冲区查找而不是随机数生成。 @Antonio 我在所有其他解决方案中也考虑了初始化/播种。 为什么不将 Randomizer 与 xorshift128plus 而不是 mt19938 结合使用?这不应该是迄今为止最快的可能性吗? @Falco 更新了 Xorshift+ 和 Randomizer 组合的基准测试。【参考方案3】:

一种方法是为每 64 个随机调用生成一个 unsigned long long,如 cmets 中所述。一个例子:

#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;
;

【讨论】:

您是否根据 OP 的示例对其进行了衡量? 您不应该在时间紧迫的循环中一遍又一遍地创建新的random_deviceuniform_int_distribution 我敢打赌,构造 mt19937 的开销比 random_deviceuniform_int_distribution 加起来的开销要高出几个数量级——也把它放在类范围内。 如果你没有在每个 RandomBool 调用上创建一个新的 std::mt19937,这将比 Xorshift128+ 稍快,至少在我的基准测试中。【参考方案4】:

我会预先填充 64 位随机值的(足够长的)(循环)缓冲区,然后在需要布尔随机值时非常快速地一次取一位

#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;
  

当然,这个类可以对缓冲区大小、随机生成器、基本类型(不一定必须是 uint64_t)等进行通用化。


每 64 次调用仅访问一次缓冲区:

#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://***.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;
  
;

【讨论】:

您可能应该在计数器 == BUFFER_SIZE 时重新填充缓冲区,以获得按随机数生成器顺序排列的重复长度... @Falco 时间关键部分(随机布尔生成)偶尔会变得非常慢,这是不可接受的。这个想法是,根据随机性需求,可以在足够长的时间后接受序列的重复。 对于一个库类,我宁愿抛出一个错误或明确声明短周期长度,否则这真的很难找到。预计算听起来不错,但短周期长度可能会是个大问题。 @Falco 你说的是对的。人们可能应该事先知道需要多少随机数,或者可以接受哪种可重复性,并且该类应该有一个清楚地说明这些限制的名称。 @Falco 无论如何,缓冲区的想法似乎适用于这个特定问题,因为我们存储的只是,并且内存占用以某种方式减少了。同样,这取决于我们需要生成多少随机布尔值。【参考方案5】:

除非您对所需的随机性有进一步的限制,否则生成随机布尔值的最快方法是:

bool RandomBool()  return false; 

更具体地说,有数千种方法可以生成随机布尔数,所有这些方法都满足不同的约束条件,其中许多都不能提供“真正的”随机数(包括迄今为止的所有其他答案)。 “随机”这个词本身并不能告诉任何人你真正需要什么属性。

【讨论】:

伪随机生成器也不提供真正的随机数,没有办法做到这一点。 @GillBates 确实有很多方法可以生成随机数字,并且比伪随机生成器生成的数字更难以预测,我认为这就是您所说的“真正随机”。他们要么使用专门的硬件,要么使用其他硬件的副作用。 “随机”是一个定义非常不明确的术语,这仍然是一个事实。【参考方案6】:

如果性能是您的唯一标准,那么answer 是:

bool get_random()

    return true; // chosen by fair coin flip.
                 // guaranteed to be random.

不幸的是,这个随机数的熵为零,但性能相当快。

由于我怀疑这个随机数生成器对您不是很有用,您需要量化您希望布尔值有多随机。 2048 的周期长度如何?一百万? 2^19937-1?直到宇宙尽头?

我怀疑,既然您明确表示性能是您最关心的问题,那么一个好的老式线性同余生成器可能“足够好”。基于this article,我猜这个生成器的周期大约是 32*((2^31)-5),或大约 68 万亿次迭代。如果这还不够“好”,您可以使用任何您喜欢的 C++11 兼容生成器来代替 minstd_rand。

为了获得额外的功劳和对性能的小幅影响,请修改以下代码以使用 biased coin algorithm 来消除生成器中的偏差。

#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;
    

【讨论】:

【参考方案7】:

如果性能很重要,也许最好生成一个 32 位随机数并使用它的每个单独的位,如下所示:

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); 
 

这样一代只会影响每 32 次调用

【讨论】:

【参考方案8】:

我认为最好的方法是使用预先计算好的随机数组:

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);

它用来填充一些数组 dst[size]:

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));

当然,您可以使用另一个随机函数来初始化预先计算的数组。

【讨论】:

您是否根据 OP 的示例对其进行了衡量? 我使用这种方法在 Rasberry Pi 中通过随机数初始化大数组。它的工作速度比调用 rand() 快得多。 小心 - rand() 的最低有效位通常是最不随机的。【参考方案9】:

显然我必须添加另一个答案。刚刚发现从Ivy Bridge 架构开始,英特尔添加了RdRand CPU 指令,AMD 在 2015 年 6 月晚些时候添加了它。因此,如果您的目标处理器足够新并且不介意使用(内联)汇编,那么最快生成随机bools 的方法应该是调用RdRand CPU 指令以获取 64 位随机数,如here 所述(滚动到页面中间的代码示例)(在该链接上还有用于检查当前 CPU 是否支持 RdRand 指令的代码示例,另请参阅 Wikipedia 以了解如何使用 CPUID 指令执行此操作),然后使用该数字的位作为布尔值,如我的 Xorshit+ based answer 中所述。

【讨论】:

以上是关于在性能方面,生成随机布尔值的最佳方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

在python中获取随机布尔值?

生成包含随机布尔值的大型 numpy 数组的内存有效方法

在 plist 中处理持久布尔值的最佳方法?

从函数返回多个值的最佳方法是啥?

从函数返回多个值的最佳方法是啥?

Java:生成一个恰好为“x”为真的随机布尔数组 - 算法