Sobol 序列并行化的实践经验

随机数发生器并行化的常见策略

随机数发生器的并行化通常有四种策略(文献【2】):

  1. “随机化”种子
  2. 参数化随机数发生器
  3. 分块策略
  4. 蛙跳策略

“随机化”种子

“随机化”种子就是让每个线程上运行的发生器使用不同的种子,这种策略常见于伪随机数。不过,使用不同随机数种子的发生器所产生的随机数是否依然保持统计意义上的独立性,这一点是未知的。

参数化随机数发生器

某些随机数发生器可以采用不同的参数配置,例如最简单的线性同余发生器,让每个线程运行不同参数配置的发生器就可以实现并行化。不过,这种策略和“随机化”种子有相同的安全隐患,无法确保产生的随机数依然保持统计意义上的独立性。另外,即使保持了独立性,可用的参数配置组合可能数量十分有限,无法充分发挥硬件的并行优势。

分块策略

要确保产生的随机数保持统计意义上的独立性,最简单的办法就是使用一个发生器,在不同的线程上返回不同的数字。要做到这一点可以采用分块生成的策略,下面举个例子。

无论是伪随机还是拟随机,发生器产生的随机数的顺序是不变的。假设要在 4 个线程上并行发生 10000 个随机数,分块策略就是让第 1 个线程返回第 1~2500 个随机数;第 2 个线程返回第 2501~5000 个随机数;第 3 个线程返回第 5001~7500 个随机数;第 4 个线程返回第 7501~10000 个随机数。

分块策略要求发生器必须有一个“长距离跳转”的功能,而且要保证计算成本尽量低。

蛙跳策略

想要使用一个发生器在不同的线程上返回不同的数字,还可以采用“蛙跳”的策略,下面举个例子。

假设要在 4 个线程上并行发生 10000 个随机数,蛙跳策略就是让第 1 个线程返回第 \(4k\) 个随机数;第 2 个线程返回第 \(4k + 1\) 个随机数;第 3 个线程返回第 \(4k + 2\) 个随机数;第 4 个线程返回第 \(4k + 3\) 个随机数。

同分块策略相比,每个线程上产生的数字是间隔开的,跳转步长等于线程数。

因为要多次跳转,蛙跳策略要求发生器必须有一个“极低成本”的“短距离跳转”的功能。

Sobol 序列的原理和跳转功能

Sobol 序列的产生依赖于一组事先确定“方向数”——\(m\),以及“异或”运算。对于多维 Sobol 序列,每个维度有各自的方向数。

Sobol 序列的发生可以表示为非递归和递归两种形式:

  • 非递归形式:\(y_n = g_1 m_1 \oplus g_2 m_2 \oplus g_3 m_3 \oplus \cdots\)
  • 递归形式:\(y_n = y_{n-1} \oplus m_{f(n-1)}, y_0 = 0\)

其中 \(\oplus\) 表示异或运算;\(g_i\) 是 \(n\) 的 Gray 码的二进制表示,\(n\) 的 Gray 码等于 \(n \oplus (n/2)\),而 \(n \oplus (n/2) = \dots g_3 g_2 g_1\);\(f(n)\) 表示 \(n\) 的二进制编码中最右侧的 0 所在的位数。

若采用 32 位整数,\(x_n = 2^{-32} y_n\),\(x_n\) 就是服从单位均匀分布的 Sobol 序列。

非递归形式表明 Sobol 序列有低成本的长距离跳转功能,因为若采用 32 位整数,直接计算 \(y_n\) 至多进行 32 次异或计算。计算 Sobol 序列的程序通常都会提供一个 skipTo 函数,实现长距离跳转。

Sobol 序列并行化实践

随机数的并行化要求精准的操作线程,因此在最常见的“单机多核”情形下可以采用 OpenMP 框架。

分块策略

假设已经具备了单线程上的 Sobol 序列发生器 SobolRsg,可以构造一个复合对象 OmpSobolRsg,内部包含 \(N\) 个完全相同的 SobolRsg 对象。在集中调用之前,事先告知 OmpSobolRsg 未来要调用的次数 \(M\),将 \(N\) 个 SobolRsg 对象分别跳转到合适的位置,第 \(i\) 个 SobolRsg 对象将专门在第 \(i\) 个线程上调用。这样就实现了 Sobol 序列的并行发生。

借助 OpenMP 框架,上述设计很容易实现,实现细节不赘述。

蛙跳策略

在蛙跳策略下,需要对 SobolRsg 对象稍加改造,以便实现从 \(y_{n}\) 直接到 \(y_{n+N}\) 的递归。和分块策略类似,需要构造一个复合对象 OmpSobolRsg,内部包含 \(N\) 个完全相同的 SobolRsg 对象。无需事先告知未来要调用的次数,只需初始化阶段将 \(N\) 个 SobolRsg 对象分别跳转,恰好错开一步。第 \(i\) 个 SobolRsg 对象将专门在第 \(i\) 个线程上调用。这样就实现了 Sobol 序列的并行发生。

借助 OpenMP 框架,上述设计也很容易实现,实现细节不赘述。

蛙跳策略的计算量分析

回想递归形式,在单线程情况下要生成 \(K\) 维的 Sobol 序列,需要进行 \(K\) 次整数间的异或计算,以及 \(K\) 次浮点数除法计算。蛙跳策略中的跳转则会触发若干次异或计算,当线程数量增多时,势必会增加计算量,削弱并行化的效果。

减少异或计算的技巧

依据递归形式,跳转 \(N\) 步的话有如下递归表达式:

\[y_n = y_{n-N} \oplus m_{f(n-N)} \oplus \cdots \oplus m_{f(n-2)} \oplus m_{f(n-1)}
\]

在原始情况下,跳转 \(N\) 步将发生 \(N\) 次异或计算。但根据 \(f(n)\) 的定义,\(m_{f(n-i)}\) 中可能有不少重合的数字。

与此同时,注意到异或计算的几个性质:

  • \(a \oplus b = b \oplus a\)
  • \(a \oplus (b \oplus c) = (a \oplus b) \oplus c\)
  • \(a \oplus a = 0\)
  • \(a \oplus 0 = a\)
  • \(0 \oplus 0 = 0\)

也就是说,偶数个 \(m\) 可以归并为 0,而 0 是可以忽略的。如果某个数字 \(m\) 出现了偶数次,可以直接忽略;如果某个数字 \(m\) 出现了奇数次,只保留一次就可以了。文献【1】中就举了这样的例子,跳转 \(2^p\) 步只需 2 次异或计算。

尽管如此,同分块策略相比蛙跳策略避免不了频繁跳转,也就避免不了多余的异或计算,这可能会导致并行 Monte Carlo 模拟的加速效率大打折扣。

分块策略不算缺点的缺点

和蛙跳策略相比,分块策略避免了反复跳转,但要求事先告知要调用的次数,这一点和随机数发生器通常的使用模式相背离。但是瑕不掩瑜,分块策略可以获得更好地加速比。笔者分别采用分块和蛙跳策略并行化了 QuantLib 的 Monte Carlo 定价框架,在 8 核 CPU 上,分块策略的加速比在 7 倍左右,而蛙跳策略的加速比在 5.4 倍左右。

图中可以看到,当线程数较少时,两种策略难分伯仲;但当线程数较多时,蛙跳策略的加速效率明显下降,这是频繁跳转造成的拖累。

参考文献

  • 【1】Parallelisation Techniques for Random Number Generators
  • 【2】Tina's Random Number Generator Library

Sobol 序列并行化的实践经验的更多相关文章

  1. CI Weekly #6 | 再谈 Docker / CI / CD 实践经验

    CI Weekly 围绕『 软件工程效率提升』 进行一系列技术内容分享,包括国内外持续集成.持续交付,持续部署.自动化测试. DevOps 等实践教程.工具与资源,以及一些工程师文化相关的程序员 Ti ...

  2. 根据实践经验,讲述些学习Java web能少走的弯路,内容摘自java web轻量级开发面试教程

    在和不少比较上进的初级程序员打交道的过程中,我们总结出了一些能帮到合格程序员尽快进阶的经验,从总体上来讲,多学.多实践不吃亏.本文来是从 java web轻量级开发面试教程从摘录的. 1  哪些知识点 ...

  3. 华为云对Kubernetes在Serverless Container产品落地中的实践经验

    华为云容器实例服务,它基于 Kubernetes 打造,对最终用户直接提供 K8S 的 API.正如前面所说,它最大的优点是用户可以围绕 K8S 直接定义运行应用. 这里值得一提是,我们采用了全物理机 ...

  4. 关于Flask使用Celery的实践经验分享

      最近大Boss反馈Celery经常出现问题,几经实践终于把问题解决了!于是乎有了这篇博客的诞生,算是一个实践经验的分享吧! 软件版本如下: Celery () Flask () RabbitMQ( ...

  5. 领域驱动设计(DDD)的实践经验分享之ORM的思考

    原文:领域驱动设计(DDD)的实践经验分享之ORM的思考 最近一直对DDD(Domain Driven Design)很感兴趣,于是去网上找了一些文章来看看,发现它确实是个好东西.于是我去买了两本关于 ...

  6. 领域驱动设计(DDD)的实践经验分享之持久化透明

    原文:领域驱动设计(DDD)的实践经验分享之持久化透明 前一篇文章中,我谈到了领域驱动设计中,关于ORM工具该如何使用的问题.谈了很多我心里的想法,大家也对我的观点做了一些回复,或多或少让我深深感觉到 ...

  7. 在单体应用的一些DDD实践经验

    阅读此文需要一定的DDD基础,如果你是第一次接触DDD读者,建议先去阅读一些DDD相关的书籍或者文章之后再来阅读本文. 背景 自从我在团队中推行DDD以来,我们团队经历了一系列的磨难--先是把核心项目 ...

  8. Java小白如何一步步学好Java,听听企业Java培训师的实践经验吧

    今天我准备给小主展示一篇Java培训老师的文章,希望能给Java小白一个学好Java的路径或者提示.以下就是原文: 从大学到现在,我使用Java已经将近20年,日常也带实习生,还在公司内部做train ...

  9. FreeWheel基于Go的实践经验漫谈——GC是大坑(关键业务场景不用),web框架尚未统一,和c++性能相比难说

    摘自:http://www.infoq.com/cn/news/2017/06/freewheel-experience-on-go Go语言是FreeWheel公司目前主要力推的一个方向,在其看来, ...

随机推荐

  1. 20210712 noip12

    考场 第一次和 hzoi 联考,成功给 sdfz 丢人 尝试戴耳罩,发现太紧了... 决定改变策略,先用1h看题,想完3题再写. T1 一下想到枚举最大值,单调栈求出每个点能作为最大值的区间,然后以这 ...

  2. 使用Eclipse对weblogic进行远程调试

    一.环境说明 weblogic12c,linux centOS 6.5,eclipse mars. 二.步骤 1.找到weblogic根目录下user_projects/domains/域名/bin/ ...

  3. golang sync.noCopy 类型 —— 初探 copylocks 与 empty struct

    问题引入 学习golang(v1.16)的 WaitGroup 代码时,看到了一处奇怪的用法,见下方类型定义: type WaitGroup struct { noCopy noCopy ... } ...

  4. call、apply、bind三者比较

    var obj={a:1}; var foo={ getA:function(item1,item2){ return this.a+item1+item2 } } // apply绑定参数为数组,一 ...

  5. Java集合:HashMap

    Hashmap是一个存储key-value的映射表. 优点: 索引数据快,查找一个数据对的时间复杂度是O(1) 增加.删除一个数据的时间复杂度是O(1) key不能重复,可以存储一个null值 存储: ...

  6. javascript Date 日期格式化 formatDate, require.js 模块 支持全局js引入 / amd方式加载

    * 引入AMD加载方式: require.js CDN https://cdn.bootcss.com/require.js/2.3.5/require.js *  创建模块文件./js/util/d ...

  7. ci框架 自定义配置方法

    系统自动在Application文件夹下生成的config.php文件,采用key-value关联数组的形式来存放配置项和值.为了使结构更清晰,手动新建另外一个配置文件myconfig.php,所采用 ...

  8. css3中的陌生词汇

    Transform transform属性是静态属性,一旦写到style里面,将会直接显示作用,无任何变化过程.transform的主要用途是用来做元素的特殊变形. 关于图形变形的基础条件当中的原点设 ...

  9. 『GoLang』结构体与方法

    结构体 结构体类型 Go 通过结构体的形式支持用户自定义类型,或者叫定制类型. Go 语言结构体是实现自定义类型的一种重要数据类型. 结构体是复合类型(composite types),它由一系列属性 ...

  10. python列表底层实现原理

    Python 列表的数据结构是怎么样的? 书上说的是:列表实现可以是数组和链表.顺序表是怎么回事?顺序表一般是数组. 列表是一个线性的集合,它允许用户在任何位置插入.删除.访问和替换元素.列表实现是基 ...