并行与分布式系统中的分布式随机数生成:基于线性同余生成器的并行伪随机数生成器(Parallel Linear Congruential Generator, LCG)
题目描述
在并行与分布式计算中,多个进程或线程需要同时生成随机数,但必须确保生成的随机数序列满足统计独立性、不重叠和可重复性等要求。本题要求设计一个基于线性同余生成器(LCG)的并行伪随机数生成算法,使得多个进程能高效地生成不重叠的随机数子序列,同时保持整体序列的统计性质。
解题过程
- 理解线性同余生成器(LCG)原理
线性同余生成器是生成伪随机数的经典方法,其递推公式为:
\[ X_{n+1} = (a \cdot X_n + c) \mod m \]
其中:
- \(X_n\) 是当前状态(种子),\(X_{n+1}\) 是下一个状态。
- \(a\) 是乘数,\(c\) 是增量,\(m\) 是模数(通常为2的幂)。
- 初始种子 \(X_0\) 决定整个序列。
LCG 是顺序生成的:每个新数依赖于前一个数。若多个进程直接使用相同 LCG,会因竞争状态导致序列重叠或统计相关性,破坏随机性。
-
设计并行化策略:跳跃法(Leapfrog Method)
跳跃法允许每个进程从整个 LCG 序列中定期抽取不重叠的子序列。其核心思想是利用 LCG 的线性性质,通过数学变换预先计算每个进程的“跳跃步长”,使各进程独立生成序列的不同部分。步骤:
a. 设总共有 \(p\) 个进程,进程编号为 \(i = 0, 1, ..., p-1\)。
b. 整个 LCG 序列为 \(X_0, X_1, X_2, ...\)。
c. 进程 \(i\) 负责生成子序列 \(X_i, X_{i+p}, X_{i+2p}, ...\),即每隔 \(p\) 个元素取一个。
d. 这等价于每个进程使用一个“跳跃版”的 LCG,其递推公式可从原 LCG 推导。 -
推导跳跃版 LCG 的参数
从 LCG 递推式可知,一次迭代相当于乘以矩阵:
\[ \begin{bmatrix} X_{n+1} \\ 1 \end{bmatrix} = \begin{bmatrix} a & c \\ 0 & 1 \end{bmatrix} \cdot \begin{bmatrix} X_n \\ 1 \end{bmatrix} \mod m \]
为了跳跃 \(p\) 步,需计算矩阵的 \(p\) 次幂:
\[ M^p = \begin{bmatrix} a & c \\ 0 & 1 \end{bmatrix}^p = \begin{bmatrix} a^p & c \cdot \frac{a^p - 1}{a - 1} \\ 0 & 1 \end{bmatrix} \mod m \]
因此,进程 \(i\) 的跳跃版 LCG 参数为:
- 乘数 \(a' = a^p \mod m\)
- 增量 \(c' = c \cdot \frac{a^p - 1}{a - 1} \mod m\)
- 模数 \(m\) 不变
- 初始种子 \(X_0^{(i)} = (a'^i \cdot X_0 + c' \cdot \frac{a'^i - 1}{a' - 1}) \mod m\),表示进程 \(i\) 从原序列的第 \(i\) 个位置开始。
- 并行算法步骤
a. 初始化:主进程(如进程0)选择全局 LCG 参数 \((a, c, m, X_0)\),并计算跳跃参数 \((a', c')\)。
b. 参数广播:将 \((a, c, m, a', c')\) 广播给所有进程。
c. 本地种子计算:每个进程 \(i\) 独立计算自己的初始种子 \(X_0^{(i)}\)(使用上述公式)。
d. 本地随机数生成:每个进程使用跳跃版 LCG 迭代生成随机数:
\[ X_{n+1}^{(i)} = (a' \cdot X_n^{(i)} + c') \mod m \]
e. 结果收集:若需要集中随机数,进程将生成本地序列发送给主进程;但通常各进程独立使用本地序列,无需通信。
-
示例与验证
假设全局 LCG 参数:\(a=5, c=3, m=16, X_0=7\),进程数 \(p=2\)。- 计算跳跃参数:
\(a' = 5^2 \mod 16 = 9\),
\(c' = 3 \cdot \frac{25 - 1}{5 - 1} \mod 16 = 3 \cdot 6 \mod 16 = 2\)。 - 进程0的初始种子:
\(X_0^{(0)} = (9^0 \cdot 7 + 2 \cdot \frac{9^0 - 1}{9 - 1}) \mod 16 = 7\)。
其序列为:\(7, (9\cdot7+2)\mod16=1, (9\cdot1+2)\mod16=11, ...\)。 - 进程1的初始种子:
\(X_0^{(1)} = (9^1 \cdot 7 + 2 \cdot \frac{9^1 - 1}{9 - 1}) \mod 16 = (15 + 2\cdot1) \mod 16 = 1\)?需精确计算:
公式中 \(\frac{a'^i - 1}{a' - 1} = \frac{9^1 - 1}{9 - 1} = 1\),所以 \(X_0^{(1)} = (9\cdot7 + 2\cdot1) \mod 16 = 1\)。
其序列为:\(1, (9\cdot1+2)\mod16=11, ...\)。 - 验证:全局序列为 \(7,6,1,8,11,10, ...\)(计算:\(X_1=6, X_2=1, X_3=8, X_4=11, ...\))。
进程0生成了 \(7,1,11,...\)(对应全局下标0,2,4,...),进程1生成了 \(1,11,...\)(对应全局下标1,3,...)。发现冲突:进程1的初始种子应为全局下标1的值6,但上面计算为1,说明公式有误。需修正。
- 计算跳跃参数:
-
修正初始种子公式
正确的初始种子应通过跳跃 \(i\) 次得到。从矩阵形式推导:
\[ \begin{bmatrix} X_i \\ 1 \end{bmatrix} = M^i \cdot \begin{bmatrix} X_0 \\ 1 \end{bmatrix}, \quad M = \begin{bmatrix} a & c \\ 0 & 1 \end{bmatrix} \]
其中 \(M^i = \begin{bmatrix} a^i & c\cdot\frac{a^i - 1}{a-1} \\ 0 & 1 \end{bmatrix}\)。
因此 \(X_i = a^i \cdot X_0 + c\cdot\frac{a^i - 1}{a-1} \mod m\)。
所以进程 \(i\) 的初始种子应为 \(X_i\),即:
\[ X_0^{(i)} = \left( a^i \cdot X_0 + c \cdot \frac{a^i - 1}{a - 1} \right) \mod m \]
使用修正公式重新计算示例:
- 进程0:\(i=0\),\(X_0^{(0)} = 7\)。
- 进程1:\(i=1\),\(X_0^{(1)} = (5^1 \cdot 7 + 3\cdot\frac{5^1 - 1}{5-1}) \mod 16 = (3 + 3\cdot1) \mod 16 = 6\)。
序列验证:进程1的跳跃版 LCG 用参数 \((a'=9, c'=2)\),从种子6开始:
第一个数:\((9\cdot6+2)\mod16=8\),对应全局序列 \(X_3=8\)(正确,因为进程1负责下标1,3,5,...)。
因此各进程生成了不重叠的子序列。
- 算法复杂度与注意事项
- 时间复杂度:每个随机数生成为 \(O(1)\),与顺序 LCG 相同。
- 空间复杂度:每个进程仅存储本地状态,\(O(1)\)。
- 注意事项:
a. 参数选择需满足 LCG 的满周期条件(如 Hull–Dobell 定理)。
b. 跳跃参数 \(a'\) 和 \(c'\) 需预先计算,可能涉及大数模幂运算,但只需一次。
c. 该算法保证各进程序列不重叠,但整体序列的统计性质依赖于基础 LCG 的质量。
d. 适用于需要可重复随机数生成的并行模拟(如蒙特卡洛方法)。
通过上述步骤,我们实现了一个基于 LCG 的并行伪随机数生成器,各进程可独立生成随机数,无需同步通信,同时保持了序列的完整性和统计特性。