Numpy: Numpy提供的PCG实施具有明显的危险自相关

创建于 2020-05-20  ·  104评论  ·  资料来源: numpy/numpy

Numpy使用的PCG生成器具有大量的自相关。 即,对于从种子产生的每个序列,存在大量从其他种子开始的相关,不重叠的序列。 “相关”是指交错两个这样的序列并测试结果,您会得到在每个序列中未单独出现的故障。

大型终端集合中的两个生成器获得其中两个序列的概率是不可忽略的。 为什么从数学角度来看会发生这种情况,这是众所周知的,但在此进行了详细说明: http

为了直接显示此问题,我使用了Numpy代码编写了这个简单的C程序: http :

./intpcgnumpy 0x596d84dfefec2fc7 0x6b79f81ab9f3e37b 0x8d7deae980a64ab0 0x6b79f81ab9f3e37b | stdbuf -oL〜/ svn / c / xorshift / practrand / RNG_test stdin -tf 2 -te 1 -tlmaxonly-多线程
使用PractRand 0.94版的RNG_test
RNG = RNG_stdin,种子=未知
测试装置=扩展,折叠=额外

rng=RNG_stdin, seed=unknown
length= 128 megabytes (2^27 bytes), time= 2.2 seconds
  Test Name                         Raw       Processed     Evaluation
  BCFN(0+0,13-2,T)                  R= +27.6  p =  1.0e-13    FAIL
  BCFN(0+1,13-2,T)                  R= +68.0  p =  2.3e-34    FAIL !!!
  BCFN(0+2,13-3,T)                  R= +90.8  p =  8.8e-43    FAIL !!!
  BCFN(0+3,13-3,T)                  R=+120.6  p =  6.9e-57    FAIL !!!!
  DC6-6x2Bytes-1                    R=  +8.9  p =  4.0e-5   mildly suspicious
  DC6-5x4Bytes-1                    R= +15.7  p =  4.3e-9   very suspicious
  [Low1/8]BCFN(0+0,13-4,T)          R= +11.6  p =  4.9e-5   unusual
  ...and 1074 test result(s) without anomalies

您甚至可以更低—您只需要相同的58个低位:

./intpcgnumpy 0x596d84dfefec2fc7 0x0579f81ab9f3e37b 0x8d7deae980a64ab0 0x6b79f81ab9f3e37b | stdbuf -oL ~/svn/c/xorshift/practrand/RNG_test stdin -tf 2 -te 1 -tlmaxonly -multithreaded

[...]
rng=RNG_stdin, seed=unknown
length= 32 gigabytes (2^35 bytes), time= 453 seconds
  Test Name                         Raw       Processed     Evaluation
  [Low1/16]FPF-14+6/32:cross        R= +11.6  p =  4.0e-10   VERY SUSPICIOUS
  [Low1/32]FPF-14+6/32:cross        R= +16.5  p =  3.2e-14    FAIL
  [Low1/32]FPF-14+6/16:cross        R= +12.8  p =  3.8e-11   VERY SUSPICIOUS
  [Low1/64]FPF-14+6/64:cross        R=  +6.8  p =  4.8e-6   mildly suspicious
  [Low1/64]FPF-14+6/32:cross        R=  +6.0  p =  1.9e-5   unusual
  [Low1/64]FPF-14+6/16:cross        R=  +5.5  p =  5.8e-5   unusual
  [Low4/32]FPF-14+6/64:all          R=  +5.8  p =  5.9e-5   unusual
  [Low4/32]FPF-14+6/32:(0,14-0)     R=  +7.7  p =  1.0e-6   unusual
  [Low4/32]FPF-14+6/32:(1,14-0)     R=  +7.7  p =  9.1e-7   unusual
  [Low4/32]FPF-14+6/32:all          R=  +6.5  p =  1.3e-5   unusual
  [Low4/64]FPF-14+6/64:all          R=  +5.9  p =  5.1e-5   unusual
  [Low4/64]FPF-14+6/64:cross        R=  +8.2  p =  3.0e-7   suspicious
  [Low4/64]FPF-14+6/32:(0,14-0)     R=  +7.6  p =  1.0e-6   unusual
  [Low8/64]FPF-14+6/64:(0,14-0)     R= +17.0  p =  2.2e-15    FAIL
  [Low8/64]FPF-14+6/64:(1,14-0)     R=  +9.1  p =  5.1e-8   mildly suspicious
  [Low8/64]FPF-14+6/64:all          R= +12.7  p =  2.1e-11   VERY SUSPICIOUS
  [Low8/64]FPF-14+6/32:(0,14-0)     R= +12.8  p =  1.7e-11   VERY SUSPICIOUS
  [Low8/64]FPF-14+6/32:all          R= +11.0  p =  9.3e-10   VERY SUSPICIOUS
  ...and 1696 test result(s) without anomalies

请注意,要获得两个生成器从两个相关种子(随机选择)启动的50%概率的更多信息,您仅需要大约一百万个随机生成(生日悖论)的生成器。 而且,如果考虑到它们并非完全从同一状态开始,而是具有显着重叠的相关序列的可能性,则您所需要的要少得多。

文献中任何明智的产生者都不会那样做。 您可以对抗性地选择MRG32k3a,SFC64,CMWC,xoshiro256 ++等的任何两个起始状态,并且只要您生成不重叠的序列,就不会看到上面的故障。 这是一个主要的缺点,当许多设备使用生成器并且假定(应该是)成对的那些序列不显示相关性时,这可能会弹出。 相关性可能导致难以检测的不良行为。

请至少在某处证明不要在多个终端或高度并行的环境中使用发电机。

对于不同的“流”,可能会发生相同的情况,因为LCG通过更改加性常数生成的序列都是相同的,但符号和加性常数的变化是相同的。 您可以在此处查看一些讨论: https : https :

numpy.random

所有104条评论

@imneme@bashtage@rkern将是这里的权限,但是我认为我们已经解决了这一点,这就是为什么我们更喜欢SeedSequence.spawn接口而不是jumped接口。 比如有这样的讨论,当我们讨论的API。 请在此处查看建议https://numpy.org/devdocs/reference/random/parallel.html并根据需要提出改进建议。

@mattip这与跳跃无关。

我认为在实践中很难进行全面更改,尽管改进文档始终是一个好主意。

对于可能具有AES-NI的用户,我可能会建议AESCounter ,对于没有高度并行设置的用户,我会建议SPECK128

对于不同的“流”,可能会发生相同的情况,因为LCG通过更改加性常数生成的序列都是相同的,但符号和加性常数的变化是相同的。

你能量化一下吗? 我可以使用相同的增量来复制故障,但是我们为增量和状态设定种子,并且我还没有观察到具有两个不同的随机增量的故障。 如果还必须仔细构造增量,则这将影响实际的生日碰撞频率。

https://gist.github.com/rkern/f46552e030e59b5f1ebbd3b3ec045759

❯ ./pcg64_correlations.py --same-increment | stdbuf -oL ./RNG_test stdin64 -tf 2 -te 1 -tlmaxonly -multithreaded
0x56b35656ede2b560587e4251568a8fed
0x93526034ed105e9e587e4251568a8fed
[
    {
        "bit_generator": "PCG64",
        "state": {
            "state": 115244779949650410574112983538102603757,
            "inc": 137507567477557873606783385380908979143
        },
        "has_uint32": 0,
        "uinteger": 0
    },
    {
        "bit_generator": "PCG64",
        "state": {
            "state": 195824235027336627448689568147458133997,
            "inc": 137507567477557873606783385380908979143
        },
        "has_uint32": 0,
        "uinteger": 0
    }
]
RNG_test using PractRand version 0.93
RNG = RNG_stdin64, seed = 0x4bf19f7b
test set = expanded, folding = extra

rng=RNG_stdin64, seed=0x4bf19f7b
length= 128 megabytes (2^27 bytes), time= 3.0 seconds
  Test Name                         Raw       Processed     Evaluation
  BCFN_FF(2+0,13-3,T)               R= +59.9  p =  3.8e-28    FAIL !!!       
  BCFN_FF(2+1):freq                 R= +89.0  p~=   6e-18     FAIL !         
  BCFN_FF(2+2):freq                 R= +39.6  p~=   6e-18     FAIL !         
  BCFN_FF(2+3):freq                 R= +14.6  p~=   6e-18     FAIL !         
  BCFN_FF(2+4):freq                 R= +10.3  p~=   5e-11   very suspicious  
  DC6-9x1Bytes-1                    R=  +7.1  p =  5.6e-4   unusual          
  DC6-6x2Bytes-1                    R= +18.9  p =  1.0e-10   VERY SUSPICIOUS 
  DC6-5x4Bytes-1                    R= +11.2  p =  1.4e-6   suspicious       
  [Low4/16]BCFN_FF(2+0):freq        R= +19.5  p~=   6e-18     FAIL !         
  [Low4/16]FPF-14+6/16:all          R=  +5.6  p =  1.0e-4   unusual          
  [Low4/16]FPF-14+6/4:all           R=  +5.9  p =  4.6e-5   unusual          
  [Low4/32]BCFN_FF(2+0):freq        R=  +6.5  p~=   2e-5    unusual          
  [Low8/32]BCFN_FF(2+0):freq        R= +15.1  p~=   6e-18     FAIL !         
  [Low8/32]FPF-14+6/32:all          R=  +8.4  p =  2.5e-7   very suspicious  
  [Low8/32]FPF-14+6/32:all2         R=  +9.0  p =  7.8e-5   unusual          
  [Low8/32]FPF-14+6/16:(0,14-0)     R= +12.4  p =  4.5e-11   VERY SUSPICIOUS 
  [Low8/32]FPF-14+6/16:all          R= +15.5  p =  5.2e-14    FAIL           
  [Low8/32]FPF-14+6/16:all2         R= +41.4  p =  2.6e-16    FAIL !         
  [Low8/32]FPF-14+6/4:(0,14-0)      R=  +6.9  p =  5.9e-6   unusual          
  [Low8/32]FPF-14+6/4:all           R=  +7.9  p =  6.6e-7   suspicious       
  ...and 871 test result(s) without anomalies

好,我再试一次。

LCG中没有多个模数为2的幂的流。 许多人在早期就相信它,甚至有很长的老论文声称用这些“流”做有趣的事情,但是几十年来,人们知道通过改变常数而获得的轨道都是模一样的加法器。常量,可能是符号change_。 我能追踪到的最远的是

Mark J. Durst,使用线性同余生成器进行并行随机数生成,
1989年冬季模拟会议论文集,IEEE出版社,1989年,第462–466页。

因此,我编写了另一个程序http://prng.di.unimi.it/corrpcgnumpy.c ,您可以在其中设置:

  • PRNG的初始状态。
  • 另一个PRNG的初始状态。
  • 第一个PRNG的任意“流常量”。
  • 第二个PRNG的任意“流常数”(它们都应为偶数或均为奇数;可以通过一些附加的摆弄来消除此限制)。
  • 固定数量的低位比特,我们将在第二个PRNG中进行对抗性设置,基本上是从第一个PRNG的相同比特开始。 其余的位将从您提供的第二个PRNG的初始状态中获取。

因此,这实际上是第一个程序的设置,但是您也可以选择常量。

./corrpcgnumpy 0x596d84dfefec2fc7 0x6b79f81ab9f3e37b 0xac9c8abfcb89f65f 0xe42e8dff1c46de8b 0x8d7deae9efec2fc7 0x6b79f81ab9f3e37b 0x06e13e5e8c92c843 0xf92e8346feee7a21 56 | stdbuf -oL ~/svn/c/xorshift/practrand/RNG_test stdin -tf 2 -te 1 -tlmaxonly -multithreaded

rng=RNG_stdin, seed=unknown
length= 4 gigabytes (2^32 bytes), time= 113 seconds
  Test Name                         Raw       Processed     Evaluation
  [Low1/8]BCFN(0+0,13-1,T)          R= +27.2  p =  4.0e-14    FAIL
  [Low1/8]DC6-6x2Bytes-1            R= +10.9  p =  4.4e-6   suspicious
  [Low1/64]DC6-5x4Bytes-1           R=  -6.4  p =1-1.4e-4   unusual
  [Low8/64]FPF-14+6/64:(0,14-0)     R=  +8.4  p =  2.2e-7   mildly suspicious
  [Low8/64]FPF-14+6/64:all          R=  +8.7  p =  1.2e-7   suspicious
  [Low8/64]FPF-14+6/32:(0,14-0)     R= +10.2  p =  5.1e-9   suspicious
  [Low8/64]FPF-14+6/32:all          R=  +9.4  p =  2.7e-8   very suspicious
  [Low8/64]FPF-14+6/16:all          R=  +5.8  p =  6.4e-5   unusual
  ...and 1439 test result(s) without anomalies

因此,无论您如何选择“流常数”,都至少有2 ^ 72个相关的子序列,就像在相同常数的情况下一样。

而且,给生成器提供了可观的松弛:即使不是我正在计算的确切起点,您使用的状态之前或之后都会有点,但是无论如何都会显示出相关性。 您可以使用其他参数轻松修改该程序。

我再重复一遍:科学文献中没有现存的现代发电机具有这种不良行为(当然,2幂次LCG会具有这种行为,但是,就上帝而言,这不是现代发电机)。

萨巴斯蒂亚诺(Sabastiano)对PCG的评论在2018年的这篇博客文章中得到了解决。

简短的版本是,如果允许您制作特定的种子,则几乎可以从任何PRNG中显示“不好看”的行为。 尽管他声称PCG具有某种独特性,但实际上PCG还是很常规的-PCG的流并不比SplitMix的流差,SplitMix的流是另一种广泛使用的PRNG。

那是完全错误的。 为了证明我是错误的,请显示来自MRG32k3a或xoshiro256 ++的两个相关的不重叠序列。

我从未说过不重叠。 向我展示xoshiro256 ++的当前可用测试。 两个种子避免重叠。

相反,我确实对PCG进行了一项测试,该测试表明您显示的“关联”本质上是一种重叠形式。

我无法像“本质上”和“表单”那样对抗FUD,但我修改了http://prng.di.unimi.it/intpcgnumpy.c,以便最初对每个PRNG进行100亿次迭代,并退出并出现错误如果生成的序列遍历另一个PRNG的初始状态,则显示消息。 这样可以保证进入Practrand的前160GB数据来自非重叠序列:

./intpcgnumpy 0x596d84dfefec2fc7 0x0579f81ab9f3e37b 0x8d7deae980a64ab0 0x6c79f81ab9f3e37b | stdbuf -oL ~/svn/c/xorshift/practrand/RNG_test stdin -tf 2 -te 1 -tlmaxonly -multithreaded
[...]
rng=RNG_stdin, seed=unknown
length= 64 gigabytes (2^36 bytes), time= 926 seconds
  Test Name                         Raw       Processed     Evaluation
  [Low1/8]FPF-14+6/64:(0,14-0)      R=  +8.8  p =  8.7e-8   mildly suspicious
  [Low1/8]FPF-14+6/64:all           R=  +6.3  p =  2.1e-5   unusual          
  [Low1/16]FPF-14+6/64:(0,14-0)     R=  +7.6  p =  1.1e-6   unusual          
  [Low1/16]FPF-14+6/64:(1,14-0)     R=  +8.3  p =  2.9e-7   mildly suspicious
  [Low1/16]FPF-14+6/64:all          R=  +8.0  p =  5.8e-7   suspicious       
  [Low1/16]FPF-14+6/32:all          R=  +7.1  p =  3.9e-6   mildly suspicious
  [Low1/64]FPF-14+6/32:cross        R=  +7.1  p =  2.6e-6   mildly suspicious
  [Low4/32]FPF-14+6/64:(0,14-0)     R= +13.5  p =  4.3e-12   VERY SUSPICIOUS 
  [Low4/32]FPF-14+6/64:all          R=  +9.0  p =  5.9e-8   very suspicious  
  [Low4/64]FPF-14+6/64:(0,14-0)     R= +11.4  p =  3.8e-10  very suspicious  
  [Low4/64]FPF-14+6/64:all          R=  +8.0  p =  5.3e-7   suspicious       
  [Low4/64]FPF-14+6/32:(0,14-0)     R= +10.3  p =  3.6e-9   suspicious       
  [Low4/64]FPF-14+6/32:all          R=  +6.1  p =  3.2e-5   unusual          
  [Low8/64]FPF-14+6/64:(0,14-0)     R= +18.6  p =  8.4e-17    FAIL           
  [Low8/64]FPF-14+6/64:(1,14-0)     R= +11.4  p =  3.9e-10  very suspicious  
  [Low8/64]FPF-14+6/64:(2,14-0)     R=  +8.3  p =  2.8e-7   mildly suspicious
  [Low8/64]FPF-14+6/64:all          R= +15.3  p =  6.9e-14    FAIL           
  [Low8/64]FPF-14+6/32:(0,14-0)     R=  +7.8  p =  7.1e-7   unusual          
  [Low8/64]FPF-14+6/32:(1,14-0)     R=  +7.2  p =  2.7e-6   unusual          
  [Low8/64]FPF-14+6/32:all          R=  +5.8  p =  6.9e-5   unusual          
  ...and 1786 test result(s) without anomalies

这种特殊的初始化数据只有56个较低的固定位,因此可以通过翻转较高的位来生成2 ^ 72个相关序列。 仅在64GB数据之后,统计故障就会发生,这表明重叠不负责相关性。 当然,在其他特定的针对性选择中,重叠可能发生在64GB之前,这是一个具体示例。 但是现在可以很容易地检查出重叠不是问题所在—生成器仅具有很多不良的内部相关性。

请遵守行为准则。 尝试用“善解人意,热情好客,友好和耐心”和“谨慎选择我们要使用的语言。我们在交流中要谨慎和尊重”的指示来保持您的意见的语调。

我从未说过不重叠。 向我展示xoshiro256 ++的当前可用测试。 两个种子避免重叠。

好吧,这很简单:确定流的长度,进行迭代,并检查两个流是否不超过初始状态。 这与我用来显示程序http://prng.di.unimi.it/intpcgnumpy.c中不相关的PCG流相同的代码。

尽管他声称PCG具有某种独特性,但实际上PCG还是很常规的-PCG的流并不比SplitMix的流差,SplitMix的流是另一种广泛使用的PRNG。

恕我直言,PCG中的自相关性要差得多。 在SplitMix实例下面没有添加剂生成器的结果,类似于Durst 1989年关于LCG的惊人结果。

但是SplitMix的非常轻微的问题是已知的, JEP 356将提供一类新的可拆分生成器LXM,试图解决这些问题。 现在也该继续前进,并以更少的缺陷取代PCG了。

两个生成器都知道潜在的问题,这是由于缺少状态混合。 如果您更改其中一个发生器的状态的位_k_,则更改将永远不会传播到位_k_以下。 在具有质数模数的LCG,F 2线性发电机,CMWC发电机等中,这不会发生。所有其他发电机都试图尽可能快地混合其状态。

与PCG和SplitMix的问题相提并论是一个红色的鲱鱼。 SplitMix有一个非常简单的基础生成器,只是加法器,但最重要的是有一个非常强大的加扰功能:Appleby的MurmurHash3哈希函数是64位终结符,已在许多上下文中广泛使用,并且斯塔福德(http://zimbry.blogspot.com/2011/09/better-bit-mixing-improving-on.html)进行了改进。 该函数的常数已经过训练,具有特定的,可测量的雪崩特性。 即使少数位的变化也趋于分布在所有输出上。 换句话说,SplitMix站在巨人的肩膀上。

相反,底层PCG发生器的LCG也存在相同的混合不足问题,但是加扰功能只是作者组装的简单的算术和逻辑运算序列,没有任何理论或统计上的保证。 如果设计它们时考虑到了底层LCG的所有序列都是相同的模加和常数,可能还有符号变化,那么就可能解决这个问题。

但是作者不知道这些序列是如此容易地彼此衍生。 从PCG技术报告(https://www.pcg-random.org/pdf/hmc-cs-2014-0905.pdf)第4.2.3节中的声明中可以轻松看出这一点:

“每次选择_c_都会产生不同的数字序列,该序列的成对输出中的任何对都不会与另一个序列相同。”

这被用作证明序列是不同的,即底层的LCG提供了多个流。 杜斯特(Durst)在1989年对此主题的否定结果没有出现在论文的任何地方。 正如我之前提到的,通过这些结果,所有这些序列都是相同的,其加法常数和符号变化可能取模(对于PCG中发生的最大功效为2幂的模数的LCG)。

我确定不会引用Durst的结果是一个_bona fide_错误,但是问题是,一旦您确信所使用的底层LCG提供了某种意义上“不同”的“流”,当它们不存在时,您最终会使用PCG之类的生成器,其中即使更改“流”,每个子序列都有2 ^ 72个非重叠的相关子序列。

谢谢大家的意见。 目前,我对诸如“ PCG是好是坏”之类的二进制判断不感兴趣。 请使用您自己的论坛进行此类讨论。 这里最重要的是numpy会做什么,而最终的判断属于numpy开发人员。 我们确实感谢大家为此次讨论带来的专业知识,但我想将其重点放在基本事实上,而不是最终的判断。 我特别欣赏定量陈述,这些陈述使我对我们拥有的净空余量有所了解。 如果我先前的判断是错误的,那是因为我过早地做出了判决,因此,感谢您在避免这种情况下的协助。 谢谢。

请注意,要获得两个生成器从两个相关种子(随机选择)启动的50%概率的更多信息,您仅需要大约一百万个随机生成(生日悖论)的生成器。

@vigna您可以2**(n/2)项发生n位碰撞的50%机会(给定或取为2的因数)。 五百万是2**19 ,因此您似乎声称危险的关联从低位的40位冲突开始,但是我还没有看到证据表明这实际上是可以观察到的。 我测试了一对共享低40位的信号,并在取消测试之前在PractRand中达到了16 TiB。 如果您观察到40位冲突的失败,那么您必须测试多少TiB才能看到它?

我坚信更改增量不会影响碰撞的可能性。 “ PCG流”的优点的进一步讨论是没有主题的。 以此为借口反复抨击“作者”是特别不受欢迎的,并且践踏了我们的行为准则。 坚持不懈意味着我们将不得不在没有您的投入的情况下继续进行。 谢谢。

@imneme这似乎与以2大方倍数跳跃的问题有关。当我构造一对具有相同增量的PCG64实例并共享较低的n位,我计算出的两者之间的距离是1 << n的倍数。 看来,您更强大的DXSM输出函数似乎也可以解决此问题。 我测试了一对PCG64DXSM实例,它们共享一个增量,状态的低64位输出到2 TiB,没有问题。

好的,这很尴尬:是一半_billion_,而不是一半_million_。 一个字母可以带来很大的不同。 我为单子表示歉意。

但是,正如我早先说过的,这是命中完全相同的起始状态的概率,而不是相关子序列明显重叠的概率。 我个人更喜欢使用没有相关子序列的PRNG,因为有很多子序列,但是正如您正确地说的那样,决定权仅在于您。

修复加扰功能,使其具有更好的混合特性,这听起来是一个完美的解决方案。

我的帖子只是为了澄清PCG和SplitMix之间的结构差异,因为前一篇帖子声称它们存在类似的问题,我认为这不是正确的说法。 您不能为SplitMix编写类似http://prng.di.unimi.it/corrpcgnumpy.c的程序。

@rkern ,您问:

@imneme这似乎与以2大方倍数跳跃的问题有关。当我构造一对具有相同增量的PCG64实例并共享较低的n位,我计算的两者之间的距离是1 << n的倍数。 看来,您更强大的DXSM输出函数似乎也可以解决此问题。 我测试了一对PCG64DXSM实例,它们共享一个增量,并且状态的低64位输出到2 TiB,没有问题。

感谢您找到并链接到去年的讨论主题。 是的,正如塞巴斯蒂亚诺在回应中指出的那样,

修复加扰功能,使其具有更好的混合特性,这听起来是一个完美的解决方案。

XSL-RR处于劣势。 相比之下,PCG论文中的原始RXS-M输出功能和新的DXSM输出功能都以加扰的方式发挥了更大的作用,因此没有显示这些问题。 DXSM(在去年的提交中添加到PCG来源中)经过专门设计,比XSL-RR更强大,但具有类似的时间性能(参见RXS-M,速度较慢)。 去年,我对DXSM进行了相当艰苦的测试,但是运行了67天,停电时间很长,导致服务器停机(UPS电池电量耗尽),并结束了测试运行,但是到那时,它已经证明自己在正常情况下都表现良好测试(测试的输出为128 TB)和2 ^ 48的跳跃(测试的输出为64 TB,因为运行速度较慢)。

如果即使不设计DXSM,RXS-M都会解决这个问题,那么一个问题是,为什么我曾经使用较弱的XSL-RR置换-为什么不总是在输出函数中使用非常强的位加扰? 答案是,它基本上可以归结为周期。 速度对人们很重要,因此您要避免进行不必要的加扰。

这是塞巴斯蒂亚诺(Sebastiano)熟悉的问题,因为他的方法和我的有很多共同点。 我们每个人都采用了长期建立的方法,该方法将无法通过现代统计测试(在我的情况下为LCG,在他的情况下为Marsaglia的XorShift LFSR),并添加了加扰的输出函数以进行兑换。 我们俩都在努力使输出函数变得便宜,而且我们都发现了我们试图用输出函数掩盖的底层生成器中的不足之处。 在他的案例中,一直显示出线性问题

但是在最近的工作中,令我非常满意的是,他还展示了有多少基于LFSR的设计具有重量级问题(反映了我自己的长期关注),而这些问题却没有被其输出功能所掩盖。 他自己的发电机通过了他的测试,这就是事实,但是回到2018年,当我看着他的新xoshiro PRNG时,在我看来,基础发电机的此测试程序突出显示的重复问题?)。

至于他的相关程序,早在2018年,当他在他的网站上对PCG进行了评论时,其中包括他编写的涉及各种问题的程序(例如人为设计的种子等),我写了一个包含许多类似程序corrsplitmix2.c 。 我不太确定塞巴斯蒂安说他不能做到的时候是什么意思,但是我承认我没有机会仔细观察他的测试程序,看看他的新程序与他的测试程序是否有实质性的不同。几年前写的

请原谅我缺乏理解-但是,在这个阶段,我可以请别人总结一下吗? 是否存在现实情况,默认PCG不安全? 切换默认值的参数是什么?

简单的方法是添加一些有关高(超高)尺寸应用程序和相关序列概率的文档。

较难的路径是替换将破坏流的输出函数。 我不确定default_rng的承诺有多强。 文档似乎没有警告这可能会更改,因此可能需要一个弃用周期才能更改。 这将需要添加新的输出功能作为独立的位生成器(或可以从PCG64进行配置,这将更加明智),然后警告用户XXXX / Y.ZZ版本以后将对其进行更改。

最困难的路径是找到新的默认rng。 第一次并不是一件容易的事,而且我认为在过去的18个月中,没有任何改变可以将针向任何特定方向移动。

我打开了有关default_rng()修改的gh-16493,我认为这与该问题无关,甚至不确定我们是否必须讨论它,我们很可能早就制定了规则,而我只是不这样做记得。


我并没有声称要完全理解此讨论,但是似乎有两件事需要弄清楚:

  1. 要确定我们有足够的进展,我必须在此方面相信罗伯特,现在对我来说,听起来就我们所知应该是正确的? (即,即使在大小可能大于使用NumPy的环境中,实际发生碰撞的可能性也可能令人尴尬地低,这是否是将来可能需要更改默认值的问题。)
  2. 我们声明:

    并支持[...]以及:math: 2^{127}

    哪一个(确切地不知道数字来自何处)听起来对我们来说可能是一个小小的夸大,我们可以考虑稍微调整一下以使其完全正确? 还是链接到一些提供更多详细信息的外部资源?

现在最简单的操作是添加PCG64DXSM BitGenerator ,这是PCG64的变体,带有“便宜乘数”和更强的DXSM输出功能。 我想每个人都同意,这是我们现在在PCG64实现中提供的XSL-RR输出功能的进一步改进,在不影响运行时性能的情况下,在统计上表现更好。 这是利基市场中的直接升级, PCG64可以用作我们提供的BitGenerator 。 我认为我们应该将其与PCG64一起添加。

顺便说一句,我更喜欢它是一个单独的名为BitGenerator而不是PCG64构造函数的选项。 这样的选项在randomgen中很棒,其目的是提供许多不同的算法和变体,但是对于numpy ,我想我们希望我们的选择是“抓紧时间”,因为尽我们所能。

我认为我们真的没有解决更改default_rng()提供的内容的政策。 当我提出它的时候,我提出了一个想法,我之所以喜欢它只​​是将其功能放到Generator()构造函数中的原因之一是,如果需要的话,我们可以弃用并转移到一个不同名称的函数。 但是,当时我们考虑的是default_rng()可能需要公开底层BitGenerator的许多细节,后来我们避免了。 因为PCG64DXSM公开相同的API( .jumped()特别)为PCG64 ,我们将有唯一要考虑的是,使用作为新的默认将改变比特流。 我认为我们应该遵循与对NEP 19中来自Generator方法的其他任何修改相同的时间表(即在X.Y.0功能发布中)。 如果愿意,我们可以选择更加谨慎,并首先将PCG64DXSM作为1.20.0和文档中的可用BitGenerator公开(但不是warn() ,太吵了,没有效果) default_rng()将变为在1.21.0使用它。

作为添加新BG的一部分,最好将注释更新到PCG64以开始充当指南,并为偏爱较新的变体提供理由。

  1. 要确定我们有足够的进展,我必须在此方面相信罗伯特,现在对我来说,听起来就我们所知应该是正确的? (即,即使在大小可能大于使用NumPy的环境中,实际发生碰撞的可能性也可能令人尴尬地低,这是否是将来可能需要更改默认值的问题。)

那可能有点太客气了。 这取决于有多少个流,从每个流中提取多少数据以及生日碰撞的风险承受能力是多少。 我还没有开始将数学运算简化为易于理解的段落,也没有提出易于遵循的建议,这就是为什么我有一段时间没有再讨论这个Github问题了。 不过,我认为这不是必须立即解决的“毛发”问题。

我将在以后再写一些内容,但是正如我在本主题中所看到的那样,我们一直在重读去年的基础。 除了塞巴斯蒂亚诺(Sebastiano)发现NumPy已装运PCG以外,没有任何改变。 去年,NumPy团队进行的分析更加深入,并考虑了更合理的情况。

我希望尽快合理地升级默认值-只是为了减少混乱。 我的意思是,不要等待贬值周期。

@imneme-非常感谢-我发现较长的内容非常有用。

过去关于它的最好的

我脑子里想表达的是我想说的内容,但我发现一年前的帖子已经在这里和其他地方都说过了(我的博客(2017), reddit我的博客(2018)) ,以及在NumPy的讨论中等等)

关于流及其自相似性@rkern为此写了一个流依赖测试器),我去年写过:

如前所述,我的博客文章中指出,PCG的流与SplitMix的流有很多共同点。

关于@mdickinson的图,对于允许您播送其整个状态的_every_ PRNG,包括基于计数器的加密状态,我们可以设计种子,在其中我们的PRNG的输出以某种方式关联(最简单的方式)是为了使PRNG状态相隔很短的距离,但通常我们可以基于对它们的工作原理的理解来做其他事情。 尽管不允许全状态播种的PRNG可以避免此问题,但这样做只是引入了一个新的问题,仅提供了对可能状态的一小部分的实际访问。

考虑流的正确方法只是需要播种更多随机状态。 对于_any_ PRNG的任何播种目的,使用1,2,3之类的小值通常是个坏主意(因为如果每个人都偏爱这些种子,则它们对应的初始序列将被过多代表)。

我们可以选择根本不将其称为流,而仅将其称为状态。 这就是Marsaglia在XorWowcounter根本不与其余状态交互,并且像LCG一样,初始值的变化实际上只是一个附加常数。

SplitMix,PCG和XorWow的流是我们所谓的“愚蠢”流。 它们构成了生成器的重要重新参数化。 但是,这是有价值的。 假设没有流,我们的PRNG将有一个有趣的接近重复42,其中42快速连续种植几次,并且仅对42进行此操作,而没有其他数字。 对于愚蠢的“只是增量”或“只是异或”流,我们实际上将避免将奇怪的重复硬编码为42; 所有数字都有它们奇怪重复的流。 (由于这个原因,我将用于修复Xoshiro 256中的近重复问题的解决方案是将Weyl序列混合使用。)

(我后来去到更深入此评论,并在这一个。)

我想说的主要收获是,几乎_任何_ PRNG,只要花一点时间和精力,您就可以构造出病理种子。 PCG处于一个不寻常的位置,它拥有一个喜欢为PCG制作看起来合理的种子的人,特别是具有手工病理学的种子(例如,塞巴斯蒂亚诺)。 这项工作的结果是,我转过身,对他的PRNG和其他长期使用的PRNG都做了同样的事情。

通常,您希望使用看起来像“随机随机”的东西来初始化PRNG状态。 即使人们愿意这样做,这也是相当普遍的。 例如,对于任何LFSR PRNG(XorShift样式,Mersenne Twister等),您都不能将其初始化为全零状态,因为它只会停留在该位置。 但是,即使是几乎为零的州也常常对LFSR产生问题(这种现象称为零地,以及C ++人士为什么赚seed_seq )。 如果您想玩“让我们做一些人为设计的播种”游戏,创建100个LFSR的初始化集合并使它们全都距离零落地相距1K并不难。 人为的初始化看起来都足够纯真的,但是它们都将同时达到汉明重量的怪异下降。

如果您使用的PCG生成器使用合理的熵进行了初始化,那很好。 如果您想使用1,2,3之类的垃圾来初始化它,那么实际上对于任何PRNG都是

但是DXSM当然更强大。 我认为这会更好,或者我不会做到,但是值得有一些看法,并意识到实际上用户不会遇到经典PCG64实现的问题。

我想将PCG64流的批评/防御(通过LCG增量)与当前讨论区分开。 虽然由于LCG的数学性而存在一定的双重性,但这并不是最初提出来的核心问题。 另外,与Sebastiano的原始评论您的回复或旧的大线程相比,这里要考虑的细节更多。 对于花更多时间在数学上的专家来说,这种联系可能更明显,但是至少对于我来说,实际后果现在更清楚了。

我想说的主要收获是,几乎_任何_ PRNG,只要花一点时间和精力,您就可以构造出病理种子。

当然可以,但不是二进制驱动/不能驱动决策。 如果我从有限的PRNG中提取太多的数字,最终PractRand会继续使用它。 该二进制事实不会使该PRNG算法失效。 摆脱那个二进制文件并建立净空的概念是我对原始PCG论文真正欣赏的一件事。 给定一个对抗性生成的病理学,我们可以看一下这种病理学从良好的熵播种中随机出现的频率。 我想对此进行量化,并将其转化为对用户的实用建议。

给定两个共享低58位且增量相同的状态(我们将在其中放一个针脚),将PCG64 XSL-RR实例与这些状态进行交织可证明PractRand在32 GiB左右可观察到的故障。 我认为要避免这种情况是合理的。 因此,让我们将其作为基准,并观察良好的种子播种频率。 幸运的是,这种对抗方案适合概率分析(并非所有人都那么友好)。 对于n实例,任何2个共享相同的低58位的概率为n**2 / 2**58 ,重复计数的乘积为2。 因此,在十亿个实例中,很可能存在这样的配对,如果交错,配对将使PractRand失败。 十亿是很多! 以我的判断,我们可能永远不会看到试图创建这么多PCG64实例的numpy程序。 numpy可能是错误的工具。

我认为避免初始状态会避免其他初始状态的任何低58位碰撞状态交叉也很合理。 我仍在尝试从逻辑上进行思考,但是我认为长度会线性而不是二次地影响概率。 如果我是对的,并且我想从每个实例中绘制16个GiB( 2**28绘制),这是我们从显示PractRand失败的每个对中抽取的金额,那么我只能使用大约2**15实例,大约32k,然后才有可能观察到交叉。 对于Python来说,仍然有很多实例! 生成的数据总量约为PB的一半,这是很大的! 但这是实用性的考虑,如果我想将概率保持在较低水平,而不是仅仅低于一半,我就必须降低其中之一。 我对这些数字并不特别担心。 我认为使用带有XSL-RR输出功能的PCG64不会出现任何真正的numpy程序问题。 但是某些应用程序可能开始接近(例如,大型分布式强化学习运行)。

让我们取出该增量引脚并加以解决。 我认为可以说,使用XSL-RR输出函数,除了状态以外,还可以通过熵播种增量不改变此特定分析。 似乎对于任何给定的一对熵播种增量,实际上存在相同数量的碰撞状态。 故意构造这些状态的具体过程看起来比以相同的低58位扑击更为复杂,但是似乎碰撞状态的数目相同,因此概率计算保持不变。 通常,这并不是PCG方案所固有的。 DXSM输出功能似乎足够强大,以至于更改增量(即使使用简单的+2 )似乎也足以抵御底层LCG的最坏情况(当距离度量给出0 ),至少就我不愿使用PractRand进行测试的程度而言。

最后,我想重申大家似乎都完全同意的观点: PCG64DXSM是个好主意。 如果没有别的,它改进的统计属性简化了我认为必须记录的思维模型,而这意味着我必须减少编写文件的工作对我的书来说是件好事。

流仍然有些相关,因为仅当我们将生成器放在同一流上时,问题才会出现。

但是在什么情况下它们将具有相同的低58位并且在同一流上? 有没有发生这种情况的用例?

我知道的一个有点现实的案例是我们去年(当我们谈论jumped )谈论的一个案例,而我在这篇文章中也谈到了,我之前已经链接到了。

流仍然有些相关,因为仅当我们将生成器放在同一流上时,问题才会出现。

不幸的是,XSL-RR并非如此。 让我们考虑两个PCG64 XSL-RR实例。 我们任意地对增量进行熵种子处理,并对其中一种状态进行熵种子处理。 我们可以为其他PractRand失败的PCG64实例构造2**70坏状态,其方式与低58位状态的相同增量失败相同。 这比同增量情况下要复杂得多。 与其以较低的增量共享较低的58位作为第一个状态,它不考虑与第一个实例相距0距离(根据您的LCG距离度量)为0的状态的状态的低58位,并考虑了增量。 我有一个建设性的证明(Python代码),但我现在必须上床睡觉,明天再清理。

@rkern ,好点。 我承认,我还没有测试过这种情况,以了解情况如何。

我想说的主要收获是,几乎_任何_ PRNG,只要花一点时间和精力,您就可以构造出病理种子。 PCG处于一个不寻常的位置,它拥有一个喜欢为PCG制作看起来合理的种子的人,特别是具有手工病理学的种子(例如,塞巴斯蒂亚诺)。 这项工作的结果是,我转过身,对他的PRNG和其他长期使用的PRNG都做了同样的事情。

正如我已经说过的,这是错误的。 我不知道xoshiro256 ++中一对相关的,不重叠的序列的示例,因为您可以在PCG中轻松找到该示例。

快速混合其整个状态的PRNG不会出现此问题。 如果您可以提供一个程序,从xoshiro256 ++生成两个相关的非重叠序列,例如我在此处发布的示例,请这样做。

至于他的相关程序,早在2018年,当他在他的网站上对PCG进行了评论时,其中包括他编写的涉及各种问题的程序(例如人为设计的种子等),我写了一个包含许多类似程序corrsplitmix2.c 。 我不太确定塞巴斯蒂安说他不能做到的时候是什么意思,但是我承认我没有机会仔细观察他的测试程序,看看他的新程序与他的测试程序是否有实质性的不同。几年前写的

上面引用的程序_选择流_。 显然,编写它很容易。

但这与PCG的问题无关。 我提供的程序让_user_选择流,然后显示相关性。

我再次邀请@inmeme为SplitMix提供程序,用户可以在其中选择两个不同的流,第一个生成器的任意初始状态,然后_then_该程序在另一个生成器中找到相关的序列,例如http:/ /prng.di.unimi.it/corrpcgnumpy.c可以。

让用户任意选择流将显示更强大的相关形式。

正如我已经说过的,这是错误的。 我不知道xoshiro256 ++中一对相关的,不重叠的序列的示例,因为您可以在PCG中轻松找到该示例。

好像我们在这里互相交谈。 我并不是说我可以为任何PRNG提出相关的,不重叠的序列,我说的是我总体上可以得出病理学的种子,如我先前编写的各种相关程序所示,其他作为Xoshiro **的重复演示程序

另外,不混合整个状态的PRNG已有很长的历史,包括XorWow,数字配方中的生成器等。Sebastiano的观点代表了观点,但他的观点认为Marsaglia会以某种方式通过添加Weyl在XorWow中使XorShift _worse_序列,因为它创建了大量类似的生成器。

好像我们在这里互相交谈。 我并不是说我可以为任何PRNG提出相关的,不重叠的序列,我说的是我总体上可以得出病理学的种子,如我先前编写的各种相关程序所示,其他作为Xoshiro **的重复演示程序

请尝试使讨论保持技术水平。 “病理”没有数学意义。

技术上检查自相关的正确方法是找到两个种子,产生两个不重叠的序列(在测试过程中不重叠,如果走得足够远,它们将不可避免地重叠),将它们交错并传递给电池测试。

如果您考虑两个重叠的序列,那么它们将与每个生成器(甚至是一个加密器)相关联,这仅仅是因为相同的输出在两个序列重叠后会发生两次,而任何合理的测试都可以得出结论。

对于每个生成器而言,使用重叠序列进行“病理播种”是一项琐碎的任务(无论“病理​​”是什么意思)。

再一次,由于您声称在其他生成器中发现了与PCG类似的相关性(如测试所示,序列不重叠),您是否可以提供一对相关的,不重叠的序列,例如xoshiro256 ++或SFC64 ?

技术上检查自相关的正确方法是找到两个种子,产生两个不重叠的序列(在测试过程中不重叠,如果走得足够远,它们将不可避免地重叠),将它们交错并传递给电池测试。

您能指出相关性定义的相关文献,以便确保对此保持“技术上正确”吗?

塞巴斯蒂亚诺,您一直希望我回答您提出的挑战。 您要指出的是与LCG的固有属性有关,即存在自相似性。 在混乱的PRNG或基于LFSR的PRNG上,您将找不到相同的问题。

但是对于其他PRNG,还会有其他弱点。

LFSR具有零域,不良状态,权重问题,线性问题,正如我们在xoshiro尝试中所了解到的那样,有时还会出现其他怪异现象,例如重复问题。

混乱的PRNG具有短周期的风险(尽管有反向计数器的人可以避免这种情况-Weyl序列FTW!)及其固有偏差。

如我所写,如果序列重叠,则测试将始终失败。 您不需要文献就可以理解“总是失败”的测试不是测试。

再一次,由于您声称在其他生成器中发现了与PCG类似的相关性(如测试所示,序列不重叠),您是否可以提供一对相关的,不重叠的序列,例如xoshiro256 ++或SFC64 ?

您似乎真的在回避这个问题。 如果您有任何证据,那么根据您的要求,很容易提供此类证据。

现在最简单的操作是添加PCG64DXSM BitGenerator ,这是PCG64的变体,带有“便宜乘数”和更强的DXSM输出功能。 我想每个人都同意,这是我们现在在PCG64实现中提供的XSL-RR输出功能的进一步改进,在不影响运行时性能的情况下,在统计上表现更好。 这是利基市场中的直接升级, PCG64可以用作我们提供的BitGenerator 。 我认为我们应该将其与PCG64一起添加。

请注意,64位“便宜乘数”具有可证明的缺陷。 很久以来就知道:

W.Hörmann和G. Derflinger,一种便携式随机数发生器,非常适合
拒绝方法,ACM Trans。 数学。 软。 19(1993),no。 4,489–495。

通常,小于模数平方根的乘数对其光谱分数f 2具有固有的限制。

通过使用65位乘法器可以轻松克服该限制,编译器将仅通过附加的“加”运算来转换该乘法器,甚至可能不会改变生成器的速度。

盖伊·斯蒂尔(Guy Steele)和我在这个问题上做了一些工作,并发布了各种大小的廉价乘数的光谱评分表: https

例如,从论文的表7中,您将获得0x1d605bbb58c8abbfd,其f 2值为0.9919。 64位乘数不能超过0.9306(本文的定理4.1)。

从统计学的角度看,混合后一切,f 2分数的提高可能完全没有被注意到。 但是考虑到您只需进行一次附加的添加操作就可以针对最相关的维度获得巨大的改进,我认为(嗯,我们认为,或者我们不会写这篇论文)值得付出努力。

塞巴斯蒂亚诺,您一直希望我回答您提出的挑战。 您要指出的是与LCG的固有属性有关,即存在自相似性。 在混乱的PRNG或基于LFSR的PRNG上,您将找不到相同的问题。

哇,花了一段时间才到达那里!

LFSR具有零域,不良状态,权重问题,线性问题,正如我们在xoshiro尝试中所了解到的那样,有时还会出现其他怪异现象,例如重复问题。

我完全同意,这就是为什么您必须加扰它们。 请注意,LFSR和F 2线性发生器是不同的。 相关,但有所不同。

像往常一样,“重复出现奇怪的问题”是一个非技术性的术语,我无法评论。

混乱的PRNG具有短周期的风险(尽管有反向计数器的人可以避免这种情况-Weyl序列FTW!)及其固有偏差。

[更新:我错过了带括号的反观察,所以我要更新我的评论。]

是的,SFC64没有此类问题(它使用计数器),因此我不会一概而论。 精心设计的混沌发生器具有可证明的,最大的最短周期长度。

像往常一样,“重复出现奇怪的问题”是一个非技术性的术语,我无法评论。

无法发表评论似乎很奇怪,因为我没有使用正确的行话-运行此程序,然后随时以最佳的方式用适当的行话描述问题,并给我以启发,然后提供任何合适的评论。 我以为您和戴维·布莱克曼(David Blackman)初露端倪时会讨论这个问题,因为我就此事与他保持了联系,但我从未见过您对此事发表评论。

不在numpy的PRNG的讨论是题外话。 请使用您自己的论坛继续进行讨论。 谢谢。

@rkern-作为标准,这似乎有点严格。 如果在Numpy的实现中存在其他实现无法共享的缺陷,那么进行讨论似乎是合理的。

我可以确认,我所指的交流并不能帮助我做出在此问题上摆在我们面前的决定。 在我们取得进展之前,我需要保持专注。

我认为了解更广泛的背景是有帮助的。 塞巴斯蒂亚诺(Sebastiano)对PCG有点了解,多年来一直在反对它。 我认为这些天有些人可能看着我们俩并睁开眼睛说“你们俩都一样糟糕”,因为我也批评了他的PRNG,但实际上我只是在他四处宣称我试图通过从不谈论他的东西来掩饰某些东西(实际上,我只是没有时间/爱好,实际上我只是以为他们很好)。

尽管他的批评很有用,但我很高兴他选择了他一生中的大部分时间来思考我的工作,但重要的是要意识到他的测试本质上是对抗性的。 他利用PCG的结构知识来设计可用于RNG测试仪和失败测试的布置。

鉴于这看起来多么可怕,似乎有理由表明,类似的对抗方法也会使许多其他发电者绊倒,而且正如我对发电方案所观察到的那样,他对PCG提出的许多担忧也将适用于其他发电方案。例如XorWow,我经常以SplitMix为例,如下所述。 (我想,没有人会特别以某种方式对SplitMix进行投资。)

例如,我们可能会对SplitMix感到非常恐惧,并表明使用默认的流常量,如果我们查看每35185个输出,则它在PRNG测试套件中会失败。 哦,不! 这是因为在内部它会将一个计数器(Weyl序列!)增加0x9e3779b97f4a7c15 (基于φ,黄金比例),但是35185 * 0x9e3779b97f4a7c15 = 0x86a100000c480245 ,其中只有设置14位,中间没有很多东西。 或者,如果我们查看每个第360998717个输出,则得出的结果等同于内部状态0x48620000800401的加法,它仅被添加了8位,并且再次使输出功能难以完全掩盖。

我们可以继续对SplitMix进行恐吓交易,然后说,如果我有两个流,一个流具有加法常量0x9e3779b97f4a7c15而另一个流具有0xdaa66d2c7ddf743f ,我们会看到一些缺陷PRNG测试套件!!! 但这是因为第二个仅是另一个的3倍。

最后,如果有人说“我要给你们两个流,都要做一些令人恐惧的事情!”,那么说他们的流基于π( 0x243f6a8885a308d3 )和_e_( 0xb7e151628aed2a6b ),我们可以肯定地说,让我们进行更多的恐吓交易,将Pi流中的每6561221343个项目与E流中的每6663276199个项目以及低端和低端混合在一起,它们会产生两个相同的序列。 然后,我继续说明,对于流a上的每个跳转,流b上都有一个匹配的跳转来给出相同的输出,因此实际上它们之间有2 ^ 64种关联方式! (而且我们可以对任何两个流执行此操作,π和_e_没什么特别的。)

回到PCG,Sebastiano的测试依靠两个PCG64 XSH RR发生器精确对准,以便匹配的输出交错。 如果我们仅将其中一个PRNG推进一小段时间,仅一点一点地破坏完美对齐,就很难检测到任何可疑的东西。

另一个类似的对抗测试(会对塞巴斯蒂亚诺造成负担)将提供给PCG64 XSH RR输出,该输出可以满足他的要求,即它们是相关的,但我们没有告诉他确切的对齐方式(它们只是在正确的一般社区中)。 他的工作是找到对齐方式以表明它们之间是相关的。

总的来说,我认为在实际情况中不会出现紧急大火的问题,但另一方面,DXSM版本更好,因为它是去年编写的,目的是精确地缓解此类问题,我希望很高兴您切换到它。

PS您可以使用以下代码从您喜欢的实数中得出神奇的Weyl加性常数:

WeylConst[r_,bits_] = BitOr[Floor[(r-Floor[r])*2^bits],1]

那是Mathematica,我将保留Python版本作为练习。

这是我为不同的增量构造低位冲突的方法。

PCG64 XSL-RR和低58位冲突的结果

❯ ./pcg64_correlations.py -m 58 | stdbuf -oL ./RNG_test stdin64 -tf 2 -te 1 -tlmaxonly -multithreaded
s0 = 0b01110010100110011101000110010010101111111001100011001011001011111001001110101010011101111101001101011000011100001111111111100001
s1 = 0b10110001011001100111100010000110101110011010101010011011010100011001011111001100010001101001001011010010110101001011101111111100
dist = 0x2eb6ec432b0ea0f4fc00000000000000
[
    {
        "bit_generator": "PCG64",
        "state": {
            "state": 152330663589051481538402839025803132897,
            "inc": 228410650821285501905570422998802152525
        },
        "has_uint32": 0,
        "uinteger": 0
    },
    {
        "bit_generator": "PCG64",
        "state": {
            "state": 235805414096687854712168706130903874556,
            "inc": 70910205337619270663569052684874994465
        },
        "has_uint32": 0,
        "uinteger": 0
    }
]
RNG_test using PractRand version 0.93
RNG = RNG_stdin64, seed = 0x12d551b8
test set = expanded, folding = extra

rng=RNG_stdin64, seed=0x12d551b8
length= 128 megabytes (2^27 bytes), time= 2.8 seconds
  no anomalies in 891 test result(s)

rng=RNG_stdin64, seed=0x12d551b8
length= 256 megabytes (2^28 bytes), time= 9.4 seconds
  no anomalies in 938 test result(s)

rng=RNG_stdin64, seed=0x12d551b8
length= 512 megabytes (2^29 bytes), time= 18.1 seconds
  no anomalies in 985 test result(s)

rng=RNG_stdin64, seed=0x12d551b8
length= 1 gigabyte (2^30 bytes), time= 31.2 seconds
  Test Name                         Raw       Processed     Evaluation
  [Low1/8]FPF-14+6/16:cross         R=  +4.9  p =  1.7e-4   unusual          
  [Low4/16]FPF-14+6/16:all          R=  +8.4  p =  2.3e-7   very suspicious  
  [Low4/16]FPF-14+6/16:all2         R=  +8.3  p =  8.1e-5   unusual          
  [Low8/32]FPF-14+6/32:all          R=  +6.3  p =  2.1e-5   mildly suspicious
  [Low8/32]FPF-14+6/16:all          R=  +5.7  p =  8.0e-5   unusual          
  ...and 1034 test result(s) without anomalies

rng=RNG_stdin64, seed=0x12d551b8
length= 2 gigabytes (2^31 bytes), time= 52.7 seconds
  Test Name                         Raw       Processed     Evaluation
  [Low4/16]FPF-14+6/32:all          R=  +7.4  p =  2.0e-6   suspicious       
  [Low4/16]FPF-14+6/16:(0,14-0)     R=  +7.7  p =  9.4e-7   unusual          
  [Low4/16]FPF-14+6/16:all          R=  +8.0  p =  5.9e-7   suspicious       
  [Low4/16]FPF-14+6/16:all2         R= +12.2  p =  2.1e-6   mildly suspicious
  [Low4/16]FPF-14+6/4:(0,14-0)      R=  +7.9  p =  6.3e-7   mildly suspicious
  [Low4/16]FPF-14+6/4:all           R=  +5.8  p =  6.7e-5   unusual          
  [Low4/16]FPF-14+6/4:all2          R= +11.5  p =  3.1e-6   mildly suspicious
  [Low8/32]FPF-14+6/32:(0,14-0)     R=  +7.8  p =  8.4e-7   unusual          
  [Low8/32]FPF-14+6/32:all          R=  +7.3  p =  2.3e-6   suspicious       
  [Low8/32]FPF-14+6/32:all2         R= +14.3  p =  3.8e-7   suspicious       
  [Low8/32]FPF-14+6/16:(0,14-0)     R=  +7.7  p =  8.8e-7   unusual          
  [Low8/32]FPF-14+6/16:(1,14-0)     R=  +7.7  p =  9.3e-7   unusual          
  [Low8/32]FPF-14+6/16:all          R=  +6.9  p =  5.3e-6   mildly suspicious
  [Low8/32]FPF-14+6/16:all2         R= +18.3  p =  8.0e-9   very suspicious  
  ...and 1078 test result(s) without anomalies

rng=RNG_stdin64, seed=0x12d551b8
length= 4 gigabytes (2^32 bytes), time= 90.2 seconds
  Test Name                         Raw       Processed     Evaluation
  [Low1/8]BCFN_FF(2+0):freq         R= +14.8  p~=   6e-18     FAIL !         
  [Low1/8]BCFN_FF(2+1):freq         R=  +7.4  p~=   1e-6    mildly suspicious
  [Low1/8]FPF-14+6/16:cross         R=  +8.4  p =  2.1e-7   very suspicious  
  [Low4/16]FPF-14+6/32:(0,14-0)     R=  +8.9  p =  8.1e-8   mildly suspicious
  [Low4/16]FPF-14+6/32:(1,14-0)     R=  +8.5  p =  1.9e-7   mildly suspicious
  [Low4/16]FPF-14+6/32:all          R=  +9.4  p =  2.4e-8   very suspicious  
  [Low4/16]FPF-14+6/32:all2         R= +23.9  p =  5.2e-11   VERY SUSPICIOUS 
  [Low4/16]FPF-14+6/16:(0,14-0)     R= +13.8  p =  2.2e-12   VERY SUSPICIOUS 
  [Low4/16]FPF-14+6/16:(1,14-0)     R= +10.0  p =  7.3e-9   suspicious       
  [Low4/16]FPF-14+6/16:all          R= +12.1  p =  8.0e-11   VERY SUSPICIOUS 
  [Low4/16]FPF-14+6/16:all2         R= +52.5  p =  1.3e-22    FAIL !!        
  [Low4/16]FPF-14+6/4:(0,14-0)      R= +12.2  p =  7.0e-11   VERY SUSPICIOUS 
  [Low4/16]FPF-14+6/4:all           R=  +7.1  p =  3.7e-6   mildly suspicious
  [Low4/16]FPF-14+6/4:all2          R= +29.8  p =  7.1e-14    FAIL           
  [Low4/16]FPF-14+6/4:cross         R=  +5.3  p =  7.8e-5   unusual          
  [Low4/32]FPF-14+6/32:(0,14-0)     R=  +7.6  p =  1.3e-6   unusual          
  [Low4/32]FPF-14+6/32:all          R=  +6.0  p =  4.4e-5   unusual          
  [Low4/32]FPF-14+6/32:all2         R=  +9.4  p =  2.9e-5   unusual          
  [Low4/32]FPF-14+6/16:(0,14-0)     R=  +7.3  p =  2.5e-6   unusual          
  [Low4/32]FPF-14+6/16:all          R=  +6.5  p =  1.4e-5   mildly suspicious
  [Low4/32]FPF-14+6/16:all2         R=  +8.2  p =  8.0e-5   unusual          
  [Low8/32]FPF-14+6/32:(0,14-0)     R= +17.2  p =  1.7e-15    FAIL           
  [Low8/32]FPF-14+6/32:(1,14-0)     R= +12.7  p =  2.3e-11   VERY SUSPICIOUS 
  [Low8/32]FPF-14+6/32:all          R= +15.3  p =  7.9e-14    FAIL           
  [Low8/32]FPF-14+6/32:all2         R= +86.1  p =  1.2e-35    FAIL !!!       
  [Low8/32]FPF-14+6/16:(0,14-0)     R= +16.8  p =  3.5e-15    FAIL           
  [Low8/32]FPF-14+6/16:(1,14-0)     R= +12.2  p =  6.6e-11   VERY SUSPICIOUS 
  [Low8/32]FPF-14+6/16:all          R= +13.1  p =  8.9e-12   VERY SUSPICIOUS 
  [Low8/32]FPF-14+6/16:all2         R= +82.1  p =  1.7e-34    FAIL !!!       
  [Low8/32]FPF-14+6/4:(0,14-0)      R= +12.8  p =  2.0e-11   VERY SUSPICIOUS 
  [Low8/32]FPF-14+6/4:(1,14-0)      R=  +9.4  p =  2.5e-8   suspicious       
  [Low8/32]FPF-14+6/4:all           R= +10.5  p =  2.2e-9    VERY SUSPICIOUS 
  [Low8/32]FPF-14+6/4:all2          R= +42.0  p =  5.8e-19    FAIL !         
  ...and 1118 test result(s) without anomalies

PCG64 DXSM和低64位冲突的结果(以更快地引发问题,尽管我看不到)

❯ ./pcg64_correlations.py -m 64 --dxsm | stdbuf -oL ./RNG_test stdin64 -tf 2 -te 1 -tlmaxonly -multithreaded
s0 = 0b10001000010110111101010101010101111100100011011111011111011111001011110101111100101101101100110101110001101101111111010101111111
s1 = 0b11000101110100011001011000001110100001001111001001100101010000101100011001010111011001100000010010011100101110001110101000011100
dist = 0x3a26b19c91e6da1d0000000000000000
[
    {
        "bit_generator": "PCG64DXSM",
        "state": {
            "state": 181251833403477538233003277050491434367,
            "inc": 46073632738916603716779705377640239269
        },
        "has_uint32": 0,
        "uinteger": 0
    },
    {
        "bit_generator": "PCG64DXSM",
        "state": {
            "state": 262946148724842088422233355148768897564,
            "inc": 125105549038853892415237434774494719583
        },
        "has_uint32": 0,
        "uinteger": 0
    }
]
RNG_test using PractRand version 0.93
RNG = RNG_stdin64, seed = 0x85cea9
test set = expanded, folding = extra

rng=RNG_stdin64, seed=0x85cea9
length= 128 megabytes (2^27 bytes), time= 2.6 seconds
  no anomalies in 891 test result(s)

rng=RNG_stdin64, seed=0x85cea9
length= 256 megabytes (2^28 bytes), time= 9.4 seconds
  no anomalies in 938 test result(s)

rng=RNG_stdin64, seed=0x85cea9
length= 512 megabytes (2^29 bytes), time= 18.5 seconds
  no anomalies in 985 test result(s)

rng=RNG_stdin64, seed=0x85cea9
length= 1 gigabyte (2^30 bytes), time= 32.3 seconds
  Test Name                         Raw       Processed     Evaluation
  [Low4/32]BCFN_FF(2+3,13-3,T)      R=  -8.3  p =1-9.5e-5   unusual          
  ...and 1035 test result(s) without anomalies

rng=RNG_stdin64, seed=0x85cea9
length= 2 gigabytes (2^31 bytes), time= 55.8 seconds
  no anomalies in 1092 test result(s)

rng=RNG_stdin64, seed=0x85cea9
length= 4 gigabytes (2^32 bytes), time= 93.1 seconds
  no anomalies in 1154 test result(s)

rng=RNG_stdin64, seed=0x85cea9
length= 8 gigabytes (2^33 bytes), time= 175 seconds
  no anomalies in 1222 test result(s)

rng=RNG_stdin64, seed=0x85cea9
length= 16 gigabytes (2^34 bytes), time= 326 seconds
  no anomalies in 1302 test result(s)

rng=RNG_stdin64, seed=0x85cea9
length= 32 gigabytes (2^35 bytes), time= 594 seconds
  no anomalies in 1359 test result(s)

rng=RNG_stdin64, seed=0x85cea9
length= 64 gigabytes (2^36 bytes), time= 1194 seconds
  no anomalies in 1434 test result(s)

rng=RNG_stdin64, seed=0x85cea9
length= 128 gigabytes (2^37 bytes), time= 2334 seconds
  no anomalies in 1506 test result(s)
...

@rkern ,感谢您分享您的代码。 添加一个选项以增加歪斜以将交错的输出馈送到未完全对齐的测试器中将是有益的。 我对此做了一些探索。

是的,我通过在最后的return之前为各种N插入bg0.advance(N)来非正式地做到这一点。 我使用低64位冲突来确保能看到一些东西。 像16这样的微小变化并不能改变很多故障,但是即使像128这样的微小变化也可以将故障延长到32 GiB。

看来我们应该将PCG64 DXSM添加为可选的位生成器,并最终使其成为默认值。 要我们执行吗?

我认为了解更广泛的背景是有帮助的。 塞巴斯蒂亚诺(Sebastiano)对PCG有点了解,多年来一直在反对它。 我认为这些天有些人可能看着我们俩并睁开眼睛说“你们俩都一样糟糕”,因为我也批评了他的PRNG,但实际上我只是在他四处宣称我试图通过从不谈论他的东西来掩饰某些东西(实际上,我只是没有时间/爱好,实际上我只是以为他们很好)。

我认为这些考虑是完全不合适的。

没有任何优点,没有任何证据地坚持攻击他人的作品(例如,SplitMix),不会使PCG混乱或Numpy的生成器变得更好。 更好的乘数或设计更好的加扰器可能会有所帮助。

当用户可以选择流时,我仍在等待显示SplitMix中相关性的测试。 明确地说,对于Numpy的生成器,我证明了以下形式的声明

∀c∀d∀x∃y相关

其中c,d是增量(“流”),x,y是初始状态。 实际上,有2 ^ 72 y。 也就是说,无论您如何选择c,d和x,都有2 ^ 72 y个显示相关性。

您为SplitMix提供的所谓的相应代码表明:

∃c∃d∃x∃y相关

也就是说,在对抗性地选择c,d,x和y可以显示相关性。

这两个陈述在强度上的差异是相当惊人的。 试图合并这两个语句是不正确的。

@ vigna ,@

我修改了将这些表达式替换为中性表达式的消息。 我仍然认为,亲自攻击讨论的另一位参与者(“塞巴斯蒂安诺对PCG有所了解,并且多年来一直对此持反对态度”)是完全不合适的。 我很惊讶不是为了你。

对于第三次也是最后一次,关于SplitMix的讨论,无论从哪个方向进行,都没有丝毫帮助。 我能理解您为什么认为它提供了所需的上下文,或者您感到不得不对另一个做出回应,但是请相信,我是在告诉您,事实是它没有提供任何可帮助我在此做出决定的信息。 你们都有自己的网站。 使用它们。

我修改了将这些表达式替换为中性表达式的消息。

谢谢。

我仍然认为,亲自攻击讨论的另一位参与者(“塞巴斯蒂安诺对PCG有所了解,并且多年来一直对此持反对态度”)是完全不合适的。 我很惊讶不是为了你。

我也不想看到那样。 但是,该消息的语气并不差。

如果你们俩都可以坚持建设性的事实陈述@vigna和@imneme,我将不胜感激。

好。 让我们从头开始:您想要一台基于LCG的流类型为2的幂的生成器,以提供便利和速度。 文献表明,基于LCG加性常数的流可能会导致您遇到问题(现在已经发生),但是让我们假设这就是您想要的。

为什么不采用具有128位状态和良好乘法器(至少65位)的LCG,并使用SplitMix的混合功能来扰乱高位,该功能已经在不同的应用程序(哈希,PRNG等)中进行了严格测试,给出出色的结果?

我敢肯定,速度差异将是微不足道的。 并且您有一些(统计上的)保证结果将取决于所有位,这就是这里的问题。

在我看来,这似乎更像是“站在巨人的肩膀上”,而不是在具有自相关问题的发生器上手工制作混合功能。

@imneme我可以使用的是有关DXSM的博客文章,该文章比旧的大型发行中的公告评论更易于链接。 它不一定要多于该注释中的内容,但包括您在此处提到的测试的当前状态将是很好的。 如果您想总结一下导致这一发展的大型发行中的一些讨论,那肯定是有用的,但并非完全必要。

@vigna

为什么不采用具有128位状态和良好乘法器(至少65位)的LCG,并使用SplitMix的混合功能来扰乱高位,该功能已经在不同的应用程序(哈希,PRNG等)中进行了严格测试,给出出色的结果?

我很抱歉这听起来很狡猾(尽管肯定会指出),但也很真诚:我期待在您的网站或arXiv上看到实施,分析,基准和PractRand结果。 我们(在合理的情况下)是这里的从业人员,而不是PRNG研究人员,并且没有足够的能力来执行此建议。 我可以看到它的含义,但是考虑到我个人时间上的其他限制,我不愿意花很多精力将其从建议转移到实现和分析上。 如果您要针对numpy解决此建议,我们需要PRNG研究人员来完成这项工作。 如果您确实在向其他人提出这个建议,请使用您的网站。

NumPy中的随机Generator -> BitGenerator -> SeedSequence体系结构是可插入的。 我认为在讨论中我们需要有人为BitGenerator打开PR,因此我们可以将其实用属性与NumPy中的属性进行比较。 一旦它成为项目的一部分,我们就可以继续对其进行测试,并可以决定将其设置为默认值。 我希望该决定将基于

  • 缺乏偏见(和其他标准?我承认专家)
  • 性能
  • 通过我们推广的规范性接口,各种类型的流冲突的可能性:使用BitGenerator.spawnSeedSequence

就个人而言,当它避免通过使用spawn接口的代码讨论BitGenerators的优点时,这种讨论使我迷失了。 我们将其推广为最佳实践是有原因的,我希望有关未来PR的讨论将集中于NumPy用户的最佳实践。

这里的结论之一可能是,我们应该只允许spawn作为一种方法,因为使用jumpedadvance可能会违反良好的做法。 专门针对此问题

@mattip @vigna指出的低位生日碰撞SeedSequence.spawn()接口。 请放心,我参与的讨论的任何部分都与正确使用我们的API有关。

它仅需使用一些#ifdef块向pcg64.c添加大约8行,即可使用

我可能会更加明确,尤其是对于那些需要它的平台进行的模拟128位数学运算。

https://github.com/rkern/numpy/compare/v1.17.4...rkern%3Awip/pcg64-dxsm

在我看来,这要容易得多,因为它使用的是“便宜”的64位乘法器。 您可以只添加一个新的输出混频器(不变),然后在随机生成器的最后一行周围添加ifdef,以获取LCG的输出然后应用混频器。

https://github.com/bashtage/randomgen/commit/63e50a63f386b5fd725025f2199ff89454321f4c#diff -879bd64ee1e2b88fec97b5315cf77be1R115

如果您愿意,您甚至可以在此时添加Murmur Hash 3,那就这么倾向于。

if语句会被编译掉吗? 我不认为我们希望在热循环中使用它们的倍数。

同样,这归结为randomgennumpy之间的目的差异。 在randomgen中创建参数化族是有意义的,但是在numpy ,我认为将旧的BitGenerator的实现与活动默认值的实现纠缠在一起不是一个好主意。 BitGenerator 。 如果我们必须对彼此的性能进行任何维护或重构,这只会使这种工作变得更糟而不是更好。

在这里同意罗伯特。 我对在1.19.0版本中放置新的位生成器没有任何疑问,它不会改变任何当前行为。

@bashtage另外,请注意, pcg_cm_random_r()使用迭代前的状态而不是迭代后的状态,因此使用#ifdefif维护相同的代码路径并不是那么简单。

if语句会被编译掉吗? 我不认为我们希望在热循环中使用它们的倍数。

不,在NumPy中, if else应该变成类似

#if defined(PCG_DXSM)
    pcg_output_dxsm(state.high, state.low)
#else 
   <old way>
#endif

这些需要在uint128版本和后备版本中分别定义,以手动将uint128切换为高电平和低电平。

@bashtage另外,请注意, pcg_cm_random_r()使用迭代前的状态而不是迭代后的状态,因此使用#ifdefif维护相同的代码路径并不是那么简单

嗯,我针对@imneme参考实现进行了测试,并使用2个不同的种子对1000个值进行了100%匹配:

https://github.com/bashtage/randomgen/blob/master/randomgen/src/pcg64/pcg_dxsm-test-data-gen.cpp

我不确定您要在那说什么。

目前尚不清楚标准PCG64 DXSM是什么。 无论哪种情况,输出函数仅使用64位操作。 您使用的版本在另一个位置使用64位乘法器甚至更快,并返回pre(而不是post)。 setseq_dxsm_128_64似乎是现有PCG64的自然扩展,仅更改输出功能。

哦,我懂了。 不,您使用了与我在C中实现的生成器不同的C ++生成器。我实现了等效的cm_setseq_dxsm_128_64 ,它在LCG迭代中使用“便宜乘数”,而不是setseq_dxsm_128_64在LCG迭代中使用大乘数。 “便宜的乘数”将在DXSM输出函数内部重用,但这是正交的设计轴。

为什么不喜欢setseq_dxsm_128_64?

@imneme表示,她最终将更改C ++版本中的官方pcg64指向cm_setseq_dxsm_128_64 ,而不是setseq_dxsm_128_64 。 与XSL-RR相比,便宜的乘数抵消了DXSM的一些额外成本。 我认为这是她花了几个月的时间进行测试的结果。

输出预迭代状态也是性能提升的一部分

以下是一些时间安排:

In [4]: %timeit p.random_raw(1000000)
3.24 ms ± 4.61 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [5]: p = rg.PCG64(mode="sequence",use_dxsm=False)

In [6]: %timeit p.random_raw(1000000)
3.04 ms ± 8.47 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [7]: import numpy as np

In [8]: p = np.random.PCG64()

In [9]: %timeit p.random_raw(1000000)
3.03 ms ± 2.54 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

全部在Ubuntu-20.04上,默认编译器。

慢6%。 对我来说似乎有点小不同。 NumPy / randomgen中的所有时序与实际的本机紧密循环中所能获得的时间都相去甚远。

比较

In [10]: x = rg.Xoroshiro128(mode="sequence")

In [11]: %timeit x.random_raw(1000000)
2.59 ms ± 35.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

到已经从C代码发布的内容(慢了150%?)。

当然,在numpy上下文中,这没什么大不了的。 在C ++上下文中,它的重要性更大,这促使将群集月的分配用于测试,并指定为官方C ++代码中的未来默认pcg64 。 这些是IMO numpy的直接动机。

股票PCG64和我的分支机构中的PCG64DXSM之间的区别(“便宜乘数”,DXSM输出函数,输出预定的状态,单独的代码路径):

[practrand]
|1> s = np.random.SeedSequence()

[practrand]
|2> pcg64 = np.random.PCG64(s)

[practrand]
|3> pcg64dxsm = np.random.PCG64DXSM(s)

[practrand]
|4> %timeit pcg64.random_raw(1000000)
100 loops, best of 3: 3.46 ms per loop

[practrand]
|5> %timeit pcg64dxsm.random_raw(1000000)
100 loops, best of 3: 2.9 ms per loop

我仍然要说的是,即使使用MSVC的专门实现,这两者之间也只是少数(比我的要多) #ifdefs 。 加上RSN MS用户将可以使用clang#13816 lang。

我们在争论代码重复吗? 我宁愿使用脱节的实现,也不用担心用#ifdefs混淆几行代码:)

尽管确实突出了需要定义“ PCG 2.0”(最好不是NumPy的GitHub问题)的绝对清晰的声明,但这只是个玩笑。

谢谢, @ rkern等。

@imneme我可以使用的是有关DXSM的博客文章,该文章比旧的大型发行中的公告评论更易于链接。 它不一定要多于该注释中的内容,但包括您在此处提到的测试的当前状态将是很好的。 如果您想总结一下导致这一发展的大型发行中的一些讨论,那肯定是有用的,但并非完全必要。

您是否有时间限制? 我已经打算这样做一段时间,并通过其他方式将其推迟,因此实际上建议的截止日期对我来说是一个有用的动力。 大概一个星期? 二?

@rkern还引用了@vigna ,他写道:

为什么不采用具有128位状态和良好乘法器(至少65位)的LCG,并使用SplitMix的混合功能来扰乱高位,该功能已经在不同的应用程序(哈希,PRNG等)中进行了严格测试,给出出色的结果?

FWIW,这种方法在原始PCG论文中进行了讨论,使用_FastHash_作为现成的哈希函数,这是一个非常相似的乘移或移位哈希函数。 在我的测试中,它不像其他排列那样快,但是质量很高。 塞巴斯蒂亚诺(Sebastiano)在他对PCG的2018年评论中也提到了这个想法,我将在我对该评论的回应的这一部分中讨论它。

在他对PCG的评论的原始版本中,他以编写自己的PCG变体结束,下面将对此进行引用:

        #include <stdint.h>

        __uint128_t x;

        uint64_t inline next(void) {
            // Put in z the top bits of state
            uint64_t z = x >> 64;
            // Update state
            x = x * ((__uint128_t)0x2360ed051fc65da4 << 64 ^ 0x4385df649fccf645)
                  + ((__uint128_t)0x5851f42d4c957f2d << 64 ^ 0x14057b7ef767814f);
            // Compute mix
            z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9;
            z = (z ^ (z >> 27)) * 0x94d049bb133111eb;
            return z ^ (z >> 31);
        }

从那以后,他将评论中的代码更新为更快的版本,该版本使用了更便宜的乘法器,并将加法常数减少到仅64位,

        #include <stdint.h>

        __uint128_t x;

        uint64_t inline next(void) {
            // Put in z the top bits of state
            uint64_t z = x >> 64;
            // Update state (multiplier from https://arxiv.org/abs/2001.05304)
            x = x * ((__uint128_t)1 << 64 ^ 0xd605bbb58c8abbfd) + 0x14057b7ef767814f;
            // Compute mix
            z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9;
            z = (z ^ (z >> 27)) * 0x94d049bb133111eb;
            return z ^ (z >> 31);
        }

我对这两个变体的问题是,排列是可逆的,因为截断状态(上半部分)是排列/加扰的-您可以向后运行它,也可以对加扰进行加扰,从而得到仅带有所有固有缺陷的截短的LCG。 我的偏好是置换/加扰整个状态,然后将其截断。 (当然,排列/加扰较少的位数将是更快的方法-像往常一样要进行权衡取舍。有理智的人可以不同意重要的一个。)

但是,当我去年撰写该文章时,他制作自己的PCG变体的工作为DXSM排列提供了非常有用的启发。

@charris您对将PCG64DXSM实现(但尚未默认)引入1.19.0感兴趣吗? 那是什么时间表? 我看到我们已经发布了1.19.0rc2,它不是引入新功能的“ _great_”。 再说一次,我对这个问题不屑一顾。 我倾向于发布1.19.0,只是记录有关更改default_rng()并在1.20.0中引入新内容。

@rkern最终的rc需要在两个星期内发布,因此我们正在寻找六月下旬的某个版本。 如果PCG64DXSM有助于测试,我赞成将其作为可选选项,但我并不真正将其视为一项新功能,更像是一个新附件。 有时,它可以帮助您进行实际操作,以获取实际的工作代码。 NumPy是趋势设定者:)

编辑:假设,当然,新代码没有大的问题,而且看起来也不像。 我也不太担心PCG64的问题,似乎没有人会在使用我们推荐的程序时遇到问题。

@imneme一个星期会很棒。 两个星期就可以了。 谢谢!

我一直在问自己一个问题,这有点不合时宜。 我们希望我们的位生成器生成随机位,但是AFAICT,大多数测试都涉及整数。 现有测试在实际测试位方面做得如何? 如果有人对这一领域更加熟悉,我将不胜感激。

正在测试的是比特流。 我们确实告诉测试软件我们正在输出的PRNG的自然字长,但这只是为了使其能够对位进行最佳折叠以最有效地引发错误,而错误往往会在低位或高位出现。不良PRNG中的单词。 如今,我们所有人都倾向于使用的软件是PractRand,此处对其测试进行了简要最好的阅读文章可能是用于TestU01 (以前的金标准测试套件)的文章。 其《用户指南》提供了有关测试的更多详细信息。

我很抱歉这听起来很狡猾(尽管肯定会指出),但也很真诚:我期待在您的网站或arXiv上看到实施,分析,基准和PractRand结果。 我们(在合理的情况下)是这里的从业人员,而不是PRNG研究人员,并且没有足够的能力来执行此建议。 我可以看到它的含义,但是考虑到我个人时间上的其他限制,我不愿意花很多精力将其从建议转移到实现和分析上。 如果您要针对numpy解决此建议,我们需要PRNG研究人员来完成这项工作。 如果您确实在向其他人提出这个建议,请使用您的网站。

我完全可以理解你的观点。 该代码和基准测试已在页面底部注释了PCG(http://prng.di.unimi.it/pcg.php)的问题以来已有两年,名称为LCG128Mix。 在我的硬件(3.60GHz的Intel®Core™i7-7700 CPU),gcc 9.2.1和-fno-move-loop-invariants -fno-unroll-loops上,它需要2.16ns的硬件。

该代码非常简单-将标准LCG与标准混合功能(Stafford改进的MurmurHash3的终结器)结合在一起。 我对其稍加修改以使其具有可编程常数:

    #include <stdint.h>
    __uint128_t x; // state
    __uint64_t c;  // stream constant (odd)

    uint64_t inline next(void) {
        // Put in z the top bits of state
        uint64_t z = x >> 64;
        // Update LCG state
        x = x * ((__uint128_t)1 << 64 ^ 0xd605bbb58c8abbfd) + c;
        // Compute mix
        z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9;
        z = (z ^ (z >> 27)) * 0x94d049bb133111eb;
        return z ^ (z >> 31);
    }

正如我之前所解释的,由于乘法器的理论问题小于模的平方根,因此65位常数比任何64位常数都好得多。

如果您对更原则性的设计感兴趣,我将运行PractRand测试。 但是,您必须考虑到,即使使用更弱的基础生成器(它只是可加性的)和较小的状态(64位),此混合函数也产生了出色的生成器SplitMix。 因此,它将比SplitMix更好,后者通过32 TB的PractRand。

而且底层的生成器是LCG,因此您具有60年代的所有常见音调:跳跃,距离等。但是,您还可以保证统计结果的每一位都取决于高64位的每一位。州。

如果您有针对其他生成器或使用其他编译器的基准测试,请告诉我。

但是,请以同样的真诚方式:仅当您真的有兴趣考虑仅使用标准组件设计“站在巨人的肩膀上”时。 我的时间也有个人限制,我很乐意做出贡献,但是我想避免花时间去没有机会考虑的发电机。

顺便说一句,BTW为了更切实地衡量所涉及的乘法器的质量,我计算了PCG DXS和其他替代方案使用的当前64位乘法器的频谱分数,从f 2到f 1。

频谱分数是判断乘数的优劣的标准方法。 0不好,1很好。 每个分数描述输出中对,三元组,四元组等的分布情况。

如Knuth在TAoCP中建议的那样,可以用经典度量,最小值或加权度量(第一个分数加上第二个分数除以2等进行归一化)恢复这七个数字,以使第一个分数更重要。 ,这是当前乘数的最小值和加权值:

0xda942042e4dd58b  0.633  0.778

有比这更好的64位常量:

0xff37f1f758180525  0.761  0.875

如果以基本相同的速度(至少,对于LCG128Mix是相同的速度)进入65位,则会得到更好的加权度量:

0x1d605bbb58c8abbfd  0.761  0.899

原因是64位乘法器对其f 2分数具有固有限制(≤0.93),正如Knuth所指出的那样,它是最相关的:

0xda942042e4dd58b5  0.795
0xff37f1f758180525  0.928
0x1d605bbb58c8abbfd  0.992

因此,第一个乘数的f 2分数中等。 第二个乘法器非常接近64位乘法器的最佳值。 65位乘法器没有这些限制,并且得分非常接近1,通常是最好的。

为了完整起见,这是所有分数:

 0xda942042e4dd58b5  0.794572 0.809219 0.911528 0.730396 0.678620 0.632688 0.639625
 0xff37f1f758180525  0.927764 0.913983 0.828210 0.864840 0.775314 0.761406 0.763689 
0x1d605bbb58c8abbfd  0.991889 0.907938 0.830964 0.837980 0.780378 0.797464 0.761493

您可以重新计算这些分数,也可以使用我和我分配的代码Guy Steele寻找自己的乘数: https :

PCG可能是numpy的默认默认prng,但是我认为它不会经受时间的考验,因为有更多有希望的方法,但是测试较少的方法。 我在下面提出一个。

半混沌SFC64是最快的统计声音发生器之一,具有最小的合理大周期。 SFC64没有跳转功能,但可以_无速度开销地扩展以支持2 ^ 63保证的唯一流_。 只需将Weyl序列与用户选择的加性常数k(必须为奇数)相加即可,而不是仅将计数器加1。 每个奇数k都会产生一个唯一的完整周期。 它需要附加的64位状态来保持Weyl常数:

typedef struct {uint64_t a, b, c, w, k;} sfcw64_t; // k = stream

static inline uint64_t sfcw64_next(sfcw64_t* s) {
    enum {LROT = 24, RSHIFT = 11, LSHIFT = 3};
    const uint64_t out = s->a + s->b + (s->w += s->k);
    s->a = s->b ^ (s->b >> RSHIFT);
    s->b = s->c + (s->c << LSHIFT);
    s->c = ((s->c << LROT) | (s->c >> (64 - LROT))) + out;
    return out;
}

有时不希望使用320位的状态,因此我尝试将其压缩为再次使用256位。 还要注意已更改的输出功能,该功能可以更好地利用Weyl序列进行位混合。 它使用128/128位混沌/结构化状态,似乎达到了很好的平衡:
/ EDIT:8月6日从输出函数+清除中删除了rotl64():

typedef struct {uint64_t a, b, w, k;} tylo64_t;

static inline uint64_t tylo64_next(tylo64_t* s) {
    enum {LROT = 24, RSHIFT = 11, LSHIFT = 3};
    const uint64_t b = s->b, out = s->a ^ (s->w += s->k);
    s->a = (b + (b << LSHIFT)) ^ (b >> RSHIFT);
    s->b = ((b << LROT) | (b >> (64 - LROT))) + out;
    return out;
}

目前在PractRand测试中已经通过了4 TB的测试,没有出现异常,到目前为止,我短暂地进行了Vigna的Hamming-weight测试,没有出现任何问题(尽管通过这些测试不能保证几乎是真正的随机输出,而是要测试prng是否有缺陷)。

注意:从统计学上讲,使用(唯一的)随机Weyl常数(大约设置了50%的位)是有利的,但是只有进一步的测试或分析才能证明这有多重要。

/编辑:清理。

@ tylo-work SFC64和Philox一起已在NumPy中使用,这与默认生成器有关。

好的,我不知道具体实施了什么,所以这仅仅是从中选择最合适的整体? 足够公平,感谢您的澄清。

我将尝试广泛测试我提议的生成器,以查看它如何与其他生成器堆叠,到目前为止,在速度,输出质量,简单性/大小/可移植性以及大规模并行使用方面,它看起来都非常不错。 但是,如果其他人也对它进行测试,我会很高兴。

我认为我们不会从头开始重新讨论默认的PRNG。 我们当前的PRNG存在一个非常具体的问题,正在寻找可解决该特定问题的紧密相关的可用变体。 我们关注的问题之一是,当前的默认PRNG公开了PRNG的某些功能,例如可跳转性,而替换它的变体仍必须公开。 SFC64(无论是我们的还是您的)都没有该功能。

@bashtage可能愿意为randomgen接受PR,以添加您的SFC64的Weyl流变体。

@ tylo-work如果您对并行执行感兴趣,则可能需要查看NumPy的SeedSequence实现。

我认为我们不会从头开始重新讨论默认的PRNG。 我们当前的PRNG存在一个非常具体的问题,正在寻找可解决该特定问题的紧密相关的可用变体。

假设您想要类似PCG-DXS的东西,则可以通过更好的常量(还有非常小的速度降低)来做进一步的改进。 例如,PCG-DXS将很快在状态相同的低112位的两个交错的相关子序列上通过两种不同类型的测试失败:

rng=PCGDXS_int112, seed=0x4d198651
length= 128 gigabytes (2^37 bytes), time= 5700 seconds
  Test Name                         Raw       Processed     Evaluation
  [Low1/64]TMFn(0+2):wl             R= +57.3  p~=   2e-27     FAIL !!
  [Low8/64]FPF-14+6/64:(1,14-0)     R= +17.5  p =  8.0e-16    FAIL
  [other failures in the same tests]
  ...and 1893 test result(s) without anomalies

请注意,我们在谈论的只是≈65536个相关序列-不用担心。

但是,您可以通过选择更好的乘法器(例如0x1d605bbb58c8abbfd)和更好的混频器(例如0x9e3779b97f4a7c15)来改进生成器。 第一个数字是65位的乘法器,具有更好的频谱分数。 第二个数字只是64位定点表示形式中的黄金分割率,众所周知,它具有很好的混合特性(请参阅有关乘法哈希的Knuth TAoCP); 例如,Eclipse Collections库使用它来混合哈希码。

结果,对于相同数量的数据,您只会使FPF失败:

rng=PCG65-DXSϕ_int112, seed=0x4d198651
length= 128 gigabytes (2^37 bytes), time= 5014 seconds
  Test Name                         Raw       Processed     Evaluation
  [Low8/64]FPF-14+6/64:(0,14-0)     R= +16.1  p =  1.5e-14    FAIL
  [other failures in the same test]
  ...and 1892 test result(s) without anomalies

实际上,如果我们进一步扩展到2TB,则PCG-DXS对于相同的交错,相关子序列将无法通过三种类型的测试:

rng=PCGDXS_int112, seed=0x4d198651
length= 2 terabytes (2^41 bytes), time= 53962 seconds
  Test Name                         Raw       Processed     Evaluation
  [Low1/32]TMFn(0+0):wl             R= +50.2  p~=   4e-23     FAIL !!
  [Low8/64]FPF-14+6/64:(1,14-0)     R=+291.1  p =  4.7e-269   FAIL !!!!!!
  [Low8/64]Gap-16:B                 R= +19.5  p =  1.4e-16    FAIL !
  [other failures in the same tests]
  ...and 2153 test result(s) without anomalies

而PCG65-DXSϕ仍然只是FPF失败:

rng=PCGDXS65ϕ_int112, seed=0x4d198651
length= 2 terabytes (2^41 bytes), time= 55280 seconds
  Test Name                         Raw       Processed     Evaluation
  [Low8/64]FPF-14+6/64:(0,14-0)     R=+232.1  p =  2.0e-214   FAIL !!!!!!
  [other failures in the same test]
  ...and 2153 test result(s) without anomalies

当然,迟早PCG65-DXSϕ也会使Gap和TMFn失效。 但是您需要看到比PCG-DXS更多的输出。

这是PCG65-DXSϕ的完整代码,它只是具有更好常数的PCG-DXS:

#include <stdint.h>

__uint128_t x; // State
uint64_t c; // Additive constant

static inline uint64_t output(__uint128_t internal) {
    uint64_t hi = internal >> 64;
    uint64_t lo = internal;

    lo |= 1;
    hi ^= hi >> 32;
    hi *= 0x9e3779b97f4a7c15;
    hi ^= hi >> 48;
    hi *= lo;
    return hi;
}

static uint64_t inline next(void) {
    __uint128_t old_x = x;
    x = x *  ((__uint128_t)1 << 64 ^ 0xd605bbb58c8abbfd) + c;
    return output(old_x);
}

边际变慢的原因是加法指令(由65位乘法器引起),并且有两个要加载的64位常数。

我一般不认可这种生成器,但是在隐藏相关性方面,PCG65-DXSDX比PCG-DXS好得多。

@Vigna ,仅供参考,我也进行了一些交错测试,并注意到在创建128个或更多交错流时,xoshiro256 **很快失败了。 有了256,它很快就失败了。 测试的重点是检查在使用线性相关性初始化每个流时PRNG的性能。 本质上,状态被初始化为s[0]=s[1]=s[2]=s[3] = k1 + stream*k2 。 然后跳过12个输出,这基本上是sfc64初始化的方式。

我意识到,这不是建议对xoshiro进行的初始化,但是仍然很有趣-有点令人担忧-对于xoshiro来说,很少有交错流的测试就可以了,但是很多失败了。

seed: 1591888413
RNG_test using PractRand version 0.95
RNG = RNG_stdin64, seed = unknown
test set = core, folding = standard (64 bit)
...
rng=RNG_stdin64, seed=unknown
length= 2 gigabytes (2^31 bytes), time= 29.6 seconds
  Test Name                         Raw       Processed     Evaluation
  [Low1/64]FPF-14+6/16:(1,14-1)     R=  +7.2  p =  3.7e-6   unusual
  [Low1/64]FPF-14+6/16:all          R=  +9.6  p =  1.8e-8   very suspicious
  ...and 261 test result(s) without anomalies

rng=RNG_stdin64, seed=unknown
length= 4 gigabytes (2^32 bytes), time= 55.5 seconds
  Test Name                         Raw       Processed     Evaluation
  [Low1/64]FPF-14+6/16:(0,14-0)     R= +13.4  p =  4.7e-12   VERY SUSPICIOUS
  [Low1/64]FPF-14+6/16:(1,14-0)     R=  +9.4  p =  2.6e-8   suspicious
  [Low1/64]FPF-14+6/16:(2,14-1)     R=  +7.7  p =  1.3e-6   unusual
  [Low1/64]FPF-14+6/16:all          R= +17.4  p =  8.8e-16    FAIL !
  ...and 275 test result(s) without anomalies

我还试图削弱SFC64和TYLO64的初始化,使其仅跳过2条输出,但是它们似乎仍然可以。
性能方面:xoshiro256 **在我的计算机上比其他两个运行速度慢33%。 TYLO64仅更新196位状态变量。 这是测试程序:

int main()
{
    //FILE* f = freopen(NULL, "wb", stdout);  // Only necessary on Windows, but harmless.
    enum {THREADS = 256};
    uint64_t seed = 1591888413; // <- e.g. this fails. // (uint64_t) time(NULL); 
    fprintf(stderr, "seed: %lu\n", seed);

    static tylo64_t tyl[THREADS];
    static sfc64_t sfc[THREADS];
    static uint64_t xo[THREADS][4];

    for (size_t i = 0; i < THREADS; ++i) {
    tyl[i] = tylo64_seed(seed + (12839732 * i), 19287319823 * i);
    sfc[i] = sfc64_seed(seed + (12839732 * i));
    xo[i][0] = xo[i][1] = xo[i][2] = xo[i][3] = seed + (12839732 * i);
    for (int j=0; j<12; ++j) xoshiro256starstar_rand(xo[i]);
    }
    static uint64_t buffer[THREADS];
    size_t n = 1024 * 1024 * 256 / THREADS;

    while (1/*n--*/) {
        for (int i=0; i<THREADS; ++i) {
        //buffer[i] = tylo64_rand(&tyl[i]);
        //buffer[i] = sfc64_rand(&sfc[i]);
            buffer[i] = xoshiro256starstar_rand(xo[i]);
        }
        fwrite((void*) buffer, sizeof(buffer[0]), THREADS, stdout);
    }
    return 0;
}

我将包含一些相关的标头代码:

typedef struct {uint64_t a, b, w, k;} tylo64_t; // k = stream

static inline uint64_t tylo64_rand(tylo64_t* s) {
    enum {LROT = 24, RSHIFT = 11, LSHIFT = 3};
    const uint64_t b = s->b, w = s->w, out = (s->a + w) ^ (s->w += s->k);
    s->a = (b + (b << LSHIFT)) ^ (b >> RSHIFT);
    s->b = ((b << LROT) | (b >> (64 - LROT))) + out;
    return out;
}

/* stream in range [0, 2^63) */
static inline tylo64_t tylo64_seed(const uint64_t seed, const uint64_t stream) {
    tylo64_t state = {seed, seed, seed, (stream << 1) | 1};
    for (int i = 0; i < 12; ++i) tylo64_rand(&state);
    return state;
}

static inline uint64_t rotl(const uint64_t x, int k) {
    return (x << k) | (x >> (64 - k));
}
static inline uint64_t xoshiro256starstar_rand(uint64_t* s) {
    const uint64_t result = rotl(s[1] * 5, 7) * 9;
    const uint64_t t = s[1] << 17;
    s[2] ^= s[0];
    s[3] ^= s[1];
    s[1] ^= s[2];
    s[0] ^= s[3];
    s[2] ^= t;
    s[3] = rotl(s[3], 45);
    return result;
}

@ tylo-work我感谢您的分析,但我确实需要关注这个问题。 如果您想继续进行讨论,我鼓励您在自己的Github存储库中发布您的作品,并在此处再发表一个帖子,邀请这里的人们参加。 其他所有人,请在此回复。 谢谢您的合作。

@imneme @rkern 1.19版本的时间已用完。

@rkern看起来PCG64DXSM不会进入1.19.0,我将在本周末发布。 如果您能写您上面提到的有关我们的变更政策/即将进行的变更的说明,我将不胜感激。

抱歉,我正在处理其他一些无关的问题。 根据我们的讨论,我认为小的延迟不是一个大问题,因为PCG64DXSM被计划为替代选项,而不是新的默认选项(至少目前如此)。

现在1.20正在启动,是时候重新审视它并转向DXSM吗?

在分支之前,我们仍有一些时间进行迁移,但是在下个星期左右开始它可能会很好。 @bashtage我想您已经准备好PCG64DXSM了,这主要是需要决定在默认流上切换开关吗?

从它的角度看,如果可以随时使用,听起来应该以1.20的价格执行。

IIRC,我们一直在等待可以链接的参考。 但是,如果随机数的人对更改感到满意,我们应该使用它。 Windows是否需要任何特殊代码?

它只是一个不同的常数和一个不同的加扰函数。 没有什么比@rkern为Windows上的原始PCG64实现编写的新颖。 我认为决定是要拥有一个完全独立的PCG64DXSM,而不是共享一些代码(以提高性能)。

rkern的WIP分支开始可能很有意义。

我说过我会写一篇关于它的博客文章,我认为@rkern想要,但是我一直在关注其他问题,但是还没有发生(抱歉)。 同时,DXSM排列已在测试中磨去,并且似乎仍比原始排列有所改进。 从线程前面的评论来看,我认为@rkern可能更喜欢更强大的输出排列,但是这样做会增加您的速度,或者(如果您偷工减料来提高速度)增加了琐碎的可预测性。

此页面是否有帮助?
0 / 5 - 0 等级