关于C#:是否有替代使用时间播种随机数生成?

Is there an alternative to using time to seed a random number generation?

我尝试在计算集群中同时运行一段代码的几个实例(大约2000个实例)。它的工作方式是提交作业,集群将在节点打开时运行它们,每个节点有几个作业。这似乎为随机数生成中的大量实例生成相同的值,而这些实例使用时间种子。

有没有一个简单的替代品可以代替?可再生性和安全性并不重要,快速生成的独特种子是。最简单的方法是什么,如果可能的话,跨平台的方法是好的。


rdtsc指令是一个相当可靠(随机)的种子。

在Windows中,它可以通过__rdtsc()内部访问。

在GNU C中,可以通过以下方式访问:

1
2
3
4
5
unsigned long long rdtsc(){
    unsigned int lo,hi;
    __asm__ __volatile__ ("rdtsc" :"=a" (lo),"=d" (hi));
    return ((unsigned long long)hi << 32) | lo;
}

指令测量处理器通电后的总伪周期。考虑到当今机器的高频率,即使两个处理器同时启动并以相同的速度计时,它们也极不可能返回相同的值。


1
2
3
4
unsigned seed;

read(open("/dev/urandom", O_RDONLY), &seed, sizeof seed);
srand(seed); // IRL, check for errors, close the fd, etc...

我还建议使用更好的随机数生成器。


PID和时间的组合应该足够获得一个独特的种子。它不是100%跨平台的,但是nix平台上的getpid(3)和Windows上的GetProcessId将为您提供99.9%的服务。这样的方法应该有效:

1
srand((time(NULL) & 0xFFFF) | (getpid() << 16));

您也可以在*nix系统上从/dev/urandom读取数据,但没有与Windows上相同的功能。


我假设您有一个启动其他进程的进程。把它传给种子使用。然后您可以让主进程为每个进程传入一个随机数作为其种子。这样一来,实际上只有一个任意的种子被选择…你可以用时间去做。

如果您没有启动其他进程的主进程,那么如果每个进程至少有一个唯一的索引,那么您可以做的是让一个进程在内存(如果共享内存)或文件(如果共享磁盘)中生成一系列随机数,然后让每个进程将该索引的第几个随机数拉出以用作其种子。

没有什么比一个种子中的一系列随机数更能使种子分布更均匀。


如果可以使用C++ 11,那么考虑EDCOX1×4。我建议你看链接以获得全面的指南。

从视频链接中提取基本信息:您不应使用srand&rand,而应使用std::random_device&std::mt19937--在大多数情况下,您需要的是:

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
#include <random>
int main() {
    std::random_device rd;
    std::mt19937 mt(rd());
    std::uniform_int_distribution<int> dist(0,99);
    for (int i = 0; i < 16; i++) {
        std::cout << dist(mt) <<"";
    }
    std::cout << std::endl;
}

窗户

提供CryptGenRandom()RtlGenRandom()。它们将为您提供一个随机字节数组,您可以将其用作种子。

您可以在msdn页面上找到文档。

Linux/Unix

您可以使用openssl的RAND_bytes()在Linux上随机获得字节数。默认使用/dev/random

组合起来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#ifdef _WIN32
  #include <NTSecAPI.h>
#else
  #include <openssl/rand.h>
#endif

uint32_t get_seed(void)
{
  uint32_t seed = 0;

#ifdef _WIN32
  RtlGenRandom(&seed, sizeof(uint32_t) );
#else
  RAND_bytes(&seed, sizeof(uint32_t) );
#endif

  return seed;
}

注意,openssl默认情况下提供了一个加密安全的prng,因此您可以直接使用它。更多信息。


只是个主意…生成一个guid(16个字节)并对其4个字节或8个字节的块求和(取决于种子的预期宽度),允许整数环绕。将结果用作种子。

guid通常封装生成它们的计算机的特性(如mac地址),这应该使得两台不同的机器最终生成相同的随机序列变得相当不可能。

这显然是不可移植的,但是为您的系统找到合适的API/库不应该太难(例如,在win32上的UuidCreate,在linux上的uuid_generate)。


不是用c std lib time()函数中以秒为单位测量的直接时间,而是使用处理器的计数器?大多数处理器都有一个自由运行的计时计数,例如在x86/x64中,有一个时间戳计数器:

The Time Stamp Counter is a 64-bit register present on all x86 processors since the Pentium. It counts the number of ticks since reset.

(该页面还有许多方法可以在不同的平台上访问这个计数器——gcc/ms-visual c/etc)

请记住,时间戳计数器并非没有缺陷,它可能无法跨处理器同步(您可能不关心您的应用程序)。省电功能可能会使处理器上下打卡(同样,你可能不在乎)。


如果唯一性很重要,您需要安排每个节点知道其他节点声明了什么ID。你可以用一个询问"有人认领身份证吗?"或者预先为每个节点安排一个尚未分配给其他节点的ID选择。

(guid使用机器的mac,因此属于"提前安排"类别。)

如果没有某种形式的协议,您将面临两个节点重叠相同ID的风险。


假设您使用的是一个合理的posix-ish系统,那么您应该使用clock_gettime。这将以纳秒为单位给出当前时间,这意味着对于所有实际用途来说,不可能获得两次相同的值。(理论上,糟糕的实现可能会有更低的分辨率,例如将毫秒数乘以100万,但即使是像Linux这样半个不错的系统也会产生真正的纳秒级结果。)