本文档介绍在ESYSim仿真器中使用到的C++标准库或第三方库。
伪随机数产生器
伪随机数产生器是仿真器中非常常用的组件,比如片上网络中的负载产生器、片上网络中的随机仲裁器等。评价伪随机数的指标通常是重复周期,重复周期越长越好。同时,伪随机数的概率分布也很重要的。对于仿真器来说,伪随机数产生器需要在两个方面优化:
- 在完整仿真过程中,伪随机数序列都不发生重复。
- 当伪随机数足够多之后,伪随机数的概率分布趋近于期望的概率分布。
- 伪随机数的序列是确定的,即给定相同的种子,伪随机数序列可以复现。
C语言的stdlib.h
中提供了rand
函数用来产生[0, RAND_MAX)
范围内的整形伪随机数。这是C语言ANSI标准中提供了唯一伪随机数产生函数。
为了弥补C/C++语言在伪随机数方面的缺陷,Boost库提供了比较丰富的伪随机数生成函数库boost/random
。在C++11标准中,这个库从Boost库引入STL库中,头文件为#include <random>
。
与C语言不同,C++11的随机数生成器包含随机数产生器和随机数分布两个部分。根据随机数的生成方法,随机数产生器包括利用硬件随机数产生器构建的随机数设备random_device
,利用算法生成伪随机数的生成器(线性同余、梅森纠缠以及借位减法),以及通过调整伪随机数序列的伪随机数生成器(消除块和洗牌排序)。每一种生成器都可以通过设置种子来复现伪随机序列。
- 线性同余引擎(LCG)
linear_congruential_engine
。预设产生器minstd_rand0
和minstd_rand
。 - 梅森纠缠引擎(Mersenne Twister)
mersenne_twister_engine
。预设产生器mt19937
和mt19937_64
。 - 借位减法引擎(Subtract with carray)
subtract_with_carry_engine
。预设产生器ranlux24_base
和ranlux48_base
。 - 消除块引擎(Discard)
discard_block_engine
。预测产生器ranlux24
和ranlux48
。 - 洗牌排序引擎(Shuffle)
shuffle_order_engine
。 预测产生器knuth_b
。
C++11提供的随机数分布包括均匀分布、伯努利分布(二项分布)、泊松分布、正态(高斯)分布以及采样分布。如果需要使用随机数分布,首先利用随机数产生器得到一个均匀分布的随机数;然后再通过随机数分布映射到需要的概率分布。
- 均匀分布:
uniform_int_distribution
(整数均匀分布)和uniform_real_distribution
(浮点均匀分布)。 - 伯努利分布:
bernoulli_distribution
、binomial_distribution
、negative_binomial_distribution
、geometric_distribution
。 - 泊松分布:
poisson_distribution
、exponential_distribution
、gamma_distribution
、weibull_distribution
、extreme_value_distribution
。 - 正态分布:
normal_distribution
、lognormal_distribution
、chi_squared_distribution
、cauchy_distribution
、fisher_f_distribution
、student_t_distribution
。 - 采样分布:
discrete_distribution
、piecewise_constant_distribution
、piecewise_linear_distribution
。
ESYSim中的randgen
模块
ESYSim中的randgen
模块用来为仿真过程产生随机数。这个模块的C++源文件和SWIG文件在路径srcs/rangen
中;python封装和测试文件在路径python/randgen
。
使用randgen
模块,需要创建EsyRandGen
对象。创建EsyRandGen
对象需要制定随机数的种子和随机数的生成算法。
EsyRandGen(long a = 1, RandGenAlg alg = C11_DEFAULT);
随机数的生成算法用枚举变量RandGenAlg
表示。EsyRandGen
选择对应的C++11随机数生成器产生随机数。 RandGenAlg
定义如下。
enum RandGenAlg
{
C11_DEVICE = 1,
C11_MINSTD_RAND0 = 2,
C11_MINSTD_RAND = 3,
C11_MT19937 = 4,
C11_MT19937_64 = 5,
C11_RANLUX24_BASE = 6,
C11_RANLUX48_BASE = 7,
C11_RANLUX24 = 8,
C11_RANLUX48 = 9,
C11_KNUTH_B = 10
};
如果创建EsyRandGen
对象时提供的alg
不属于上面的枚举类型,则EsyRandGen
采用C语言标准定义的rand
函数产生随机数。
EsyRandGen
对象提供两类分布:均匀分布和高斯(正态)分布。由于C++是强类型语言,EsyRandGen
提供了不同的函数用来产生不同数据范围的随机数。
- 生成均匀分布的函数如下:
double flatDouble(double low, double high);
long flatLong(long low, long high);
unsigned long flatUnsignedLong(unsigned long low, unsigned long high);
unsigned long long flatUnsignedLongLong(unsigned long long low, unsigned long long high);
每一个函数产生在[low, high)
之间的一个随机数。
- 生成高斯分布的函数如下:
double gaussDouble(double mean, double variance);
long gaussLong(long mean, double variance);
unsigned long gaussUnsignedLong(unsigned long mean, double variance);
unsigned long long gaussUnsignedLongLong(unsigned long long mean, double variance);
函数产生的高斯分布满足均值为mean
而且方差为variance
。
randgen
模块的测试
randgen
模块的测试程序在路径python/randgen/test.py
。 由于使用的是C和C++11官方提供的随机数生成器,所以我们默认了伪随机序列的重复性能够满足要求,不进行测试。我们着重测试的是生成的随机序列的概率分布。测试程序采用最为基本的均匀分布的概率分布作为测试项目。
测试程序会测试由不同随机数生成算法得到的均匀分布的方差和执行速度。测试程序使用flatUnsignedLong
产生规定范围内的整形随机数。统计产生的随机数,可以得到每一个数值出现的频次,对频次求方差作为均匀分布的方差。
均匀分布的范围由min_range
和max_range
确定;每一种随机数产生算法需要对seed_count
种随机数种子进程测试;每一个随机数产生算法和随机数种子的测试需要产生number
个随机数。
根据网络负载产生的场景,我们假设:均匀分布的范围是[0, 99)
;总共测试50
个随机数种子;并且每组测试的随机数为100000
个随机数。经过多次测试,C语言标准函数rand
以及Minstd_rand0
、Ministd_rand
、Mt19937
和Mt19937_64
四种算法的方差接近,一般都是小于0.001。具体数值随随机数种子波动。另外5种方法的方差较大,一般都大于0.001。
执行时间上,C语言标准函数rand
的执行时间最小;Minstd_rand0
、Ministd_rand
、Mt19937
和Mt19937_64
四种算法的执行时间大于rand
。另外5种方法的执行时间较大。
此外,我们发现不同算法的最大偏差(某个数值出现的频次相对于理想均匀分布的频次的偏差)相对稳定(大于10%),并且无法下降。用于片上网络仿真器,这是一个较大的缺陷。如果用这样的随机数产生通信负载,某个目标地址可能比其他地址少10%的通信流量;或者多10%的通信流程。本人才疏学浅,暂时没有找到能够获得最大偏差更小的随机数算法。