LevelDB的Random实现原理

223 阅读3分钟

在前面已经较为详细的介绍了SkipList,也使用java 做了一个简单的实现。LevelDB中的SkipList实现比较经典,我看的时候觉得上头的就是Random,所以单独拿出来

random方法

首先来看下Random的在RandomHeight方法中的使用方式:

template <typename Key, class Comparator>
int SkipList<Key, Comparator>::RandomHeight() {
  // Increase height with probability 1 in kBranching
  static const unsigned int kBranching = 4;
  int height = 1; // 初始level是1
  while (height < kMaxHeight && rnd_.OneIn(kBranching)) {// 根据概率,默认1/4
    height++;
  }
  assert(height > 0);
  assert(height <= kMaxHeight); // 当前的level是不是小于kMaxHeight(默认12)
  return height;
}

其中最要的是这个rnd_.OneIn 方法,这个方法就是生成均匀分布的随机数。

现在的随机算法都是伪随机,算法为线性同余生成器,算法表示为:

Xn+1 = (a * Xn + c) % m

其中,Xn 是当前生成的伪随机数,Xn+1 是下一个生成的伪随机数,a 是乘法因子,c 是增量,m 是模数。通常,a16807c0m2147483647,这是因为这组参数在计算中具有良好的性质,可以生成均匀分布的伪随机数。

在leveldb中就使用到了上面提到的两个魔法数,具体是现在在util/random.h,因为是一个整体,所以全部复制出来,如下:

class Random {
 private:
  uint32_t seed_;
​
 public:
  explicit Random(uint32_t s) : seed_(s & 0x7fffffffu) { //0x7fffffff u表示无符号 2^31-1 二进制 111 1111 1111 1111 1111 1111 1111 1111 将第一位取0 ,因为无符号转化为有符号的时候头部位1 表示为负数
                                                          // 所以此处的主要目的是确保传入的s为正数,默认传入的seed初始值为0xdeadbeef 二进制为1101 1110 1010 1101 1011 1110 1110 1111
    // Avoid bad seeds.
    if (seed_ == 0 || seed_ == 2147483647L) {
      seed_ = 1;
    }
  }
  uint32_t Next() {
    static const uint32_t M = 2147483647L;  // 2^31-1
    static const uint64_t A = 16807;        // bits 14, 8, 7, 5, 2, 1, 0 //无符号64位二进制0100 0001 1010 0111
    // We are computing
    //       seed_ = (seed_ * A) % M,    where M = 2^31-1
    //
    // seed_ must not be zero or M, or else all subsequent computed values
    // will be zero or M respectively.  For all other values, seed_ will end
    // up cycling through every number in [1,M-1]
    uint64_t product = seed_ * A; //26696993619177 二进制 High[000000 00000 0000 0000 1100 0010 0011  1] low [111 00000 1101 0010 0011 1100 1110 1001]
     //这里是计算Mod算法的一个优化,一个64为的数,可以分为高33 位和低31 位的数
    // Compute (product % M) using the fact that ((x << 31) % M) == x. product=high<<31+low 又因为product=seed_*A,所以此时product=(high*M+high+low)%M 其中 M = 2^31-1
    // 因为seed_ 和A 中,seed_的值在初始化的时候就让他小于2^31-1,A 是固定的16807,所以这两个值都不会大于M的值,所以取余最后的结果就等(high+low)%M=high+low,所以下面的这个计算是获取high和low的值相加,也就得到了seed_
    // 但是有一种情况就是product的low为刚好但是 2^31-1,这个时候product=(high*M+high+1*M)%M=high ,但是使用下面的结果会是high+M,因为M&M=M。所以,需要判断是否seed_ 比M大,然后前去M,直接使用high,也确保了seed的值一直小于M
    // 最后强制转换为32位的值
    seed_ = static_cast<uint32_t>((product >> 31) + (product & M)); // High+low [000000 00000 0000 0000 1100 0010 0011  1]+[111 00000 1101 0010 0011 1100 1110 1001] & [111 1111 1111 1111 1111 1111 1111 1111]
    // The first reduction may overflow by 1 bit, so we may need to
    // repeat.  mod == M is not possible; using > allows the faster
    // sign-bit-based test.
    if (seed_ > M) {
      seed_ -= M;
    }
    return seed_;
  }
  // 在[0,n-1]之间返回随机数
  // Returns a uniformly distributed value in the range [0..n-1]
  // REQUIRES: n > 0
  uint32_t Uniform(int n) { return Next() % n; }
  // n分之一的概率返回true
  // Randomly returns true ~"1/n" of the time, and false otherwise.
  // REQUIRES: n > 0
  bool OneIn(int n) { return (Next() % n) == 0; }
  // //首先求得[0,max_log]的一个随机数,然后求得[0,2^maxlog-1]的一个随机数
  // Skewed: pick "base" uniformly from range [0,max_log] and then
  // return "base" random bits.  The effect is to pick a number in the
  // range [0,2^max_log-1] with exponential bias towards smaller numbers.
  uint32_t Skewed(int max_log) { return Uniform(1 << Uniform(max_log + 1)); }
};
}

上面的方法就随机数的seed计算比较上头。因为第一个种子确定后,后面的种子都相对确定了,而且这个出现的概率比较均匀,所以也就是OneIn能够保证出现概率接近的原因。