在Cython中生成随机数的正确方法是什么?

24

在Cython中生成一个介于[0,1]之间的随机数,最有效和可移植的方法是什么?其中一种方法是使用C库中的INT_MAXrand()函数:

from libc.stdlib cimport rand
cdef extern from "limits.h":
    int INT_MAX
cdef float randnum = rand() / float(INT_MAX)

这种使用方式是否可行呢?我注意到它与Python的最大整数常量相比差异很大:

import sys
print INT_MAX
print sys.maxint 
产生:
2147483647  (C max int)
9223372036854775807  (python max int)

如何确定rand()的正确“规范化”数?编辑另外,如果使用从libc调用rand()的C方法,如何设置随机种子(例如基于当前时间种子)?


你是否尝试过对几千个样本取最大值和最小值,然后看哪个缩放因子接近于 1.0 - phs
请观看“rand()被视为有害”视频。 - Bryce Guinta
5个回答

14

C标准规定rand函数返回的是一个范围在0到RAND_MAX(包括)之间的int值,因此将其除以RAND_MAX(来自于)是正确的归一化方式。实际上,RAND_MAX几乎总是等于MAX_INT,但不要依赖这一点。

由于rand函数从C89开始就已经成为ISO C的一部分,因此保证在任何地方都可以使用,但是对于它的随机数质量没有做出保证。如果可移植性是您的主要考虑因素,那么rand是最好的选择,除非您愿意使用Python的random模块。

Python中的sys.maxint是完全不同的概念;它只是Python能够表示的最大正数,超过该数将需要使用长整型。Python中的整数和长整数与C并没有特别的关系。


谢谢!您知道如何使用libc方法设置种子吗? - user248237
1
我会使用random.randint(0, INT_MAX)来实现,因为开销不是问题,因为它只会发生一次。 - Cairnarvon
开销是一个主要问题,因为这个函数在循环中被多次调用,而该循环需要生成随机数(以便在部分中进行多项式采样)。我能否设置random.randint(0, INT_MAX),然后调用C的rand(),让C和Python的种子"同步"? - user248237
澄清一下:开销仅在生成数字时才是问题,当然,正如你所说,调用 Python 一次以设置种子根本不是问题。 - user248237
1
我的意思是将random.randint(0, INT_MAX)作为libc的srand函数的参数。 - Cairnarvon

4

我不确定drand是否是新添加的,但它似乎可以完全满足你的需求,同时避免了昂贵的除法计算。

cdef extern from "stdlib.h":
    double drand48()
    void srand48(long int seedval)

cdef extern from "time.h":
    long int time(int)

# srand48(time(0))
srand48(100)
# TODO: this is a seed to reproduce bugs, put to line of code above for
# production
drand48() #This gives a float in range [0,1)

我在研究你的除法方法是否产生足够的随机性时,遇到了这个想法。我找到的来源指出,在我的情况下,我将随机数与具有两位小数的十进制数进行比较,因此我只需要三位小数的精度就足够了。所以 INT_MAX 足够了。但是, drand48 似乎可以节省除法的成本,所以值得使用。

3
顺便说一下,这不是可移植的,因为srand48drand48只在POSIX系统上可用。 - Bryce Guinta

3

我正在使用这个作为Cython函数中的内部循环,而调用Python来执行此操作成本太高。 - user248237
在我的系统上,RAND_MAX为2147483647(2 ** 31-1)。它保证至少为2 ** 15-1,但我认为实际上不会那么低。 - Cairnarvon
哦,显然 msvc 的是 2**15-1。那太糟糕了。 - Cairnarvon
是的。原因是时间缩短。使用本地C rand()函数比使用numpy.rand()少了300毫秒的时间。 - Ahmed Gad

3

以上所有答案都是正确的,但我想补充一点,这个注意点让我花了太长时间才意识到。C rand()函数是不线程安全的。因此,如果您在没有GIL的并行cython中运行,标准的C rand()函数有可能会在尝试处理所有内核调用时导致巨大的减速。这只是一个警告。


1
这是个好观点。我认为它可能会在每个线程中生成相同的“随机”数字,或者只是破坏其内部状态或其他一些东西。你看到的减速是“非线程安全”的一种表现方式。 - DavidW

0
如下所示,我认为没有理由不选择使用 Python 的 random.random()
import numpy as np
from ext.random import random as rd
%timeit rd()
48.6 ns ± 0.396 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)

np.unique([rd() for _ in range(10000000)]).size
Out[5]: 32768

from random import random as rd_python
%timeit rd_python()
33.2 ns ± 0.213 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)

np.unique([rd_python() for _ in range(10000000)]).size
Out[8]: 10000000

对于Cython版本:

from libc.stdlib cimport rand, RAND_MAX

cpdef float random():
    return float(rand()) / RAND_MAX 

这个测试并不是很好。它显示从Python调用它们大致需要相同的时间。然而,如果你从Cython本身调用它们,你可以显著加快调用C函数的速度,但是调用np.random时却不能。 - undefined
根据我的发现,对于较大的随机数计数,调用一次大的np.random可能比多次调用cython随机数更快;所以这并不是那么明显。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接