Python 中的快速 GUID

Fast GUIDs in Python

我在 Python 中开发了一个性能关键且 CPU 密集型应用程序。为了达到可接受的性能,我使用 PyPy 和使用 os.fork 生成的多个进程。总的来说,我现在对接近编译语言的性能感到满意。但是我最初使用时必须生成很多 GUID:

import uuid
print(str(uuid.uuid4()))

生成 UUID。但分析显示 UUID 生成约占总运行时间的 20%。这似乎是由于 uuid.uuid4() 中对 os.urandom() 的调用在某些系统(包括 OS X)上似乎很慢。然后我将 os.urandom() 替换为 random.getrandbits():

 import random
 str(UUID(int=random.getrandbits(128), version=4))

哪个快。但现在事实证明,我的并行 运行 工作进程大量生成重复的 GUID。

我怀疑 python 的伪数生成器是用当前时间作为种子的,这就是为什么不同的进程会生成相同的随机数。我能想到的一种可能的修复方法是将进程 ID 包含到数字生成器的种子中。但是由于我不确定 uuid 和 random 包是如何在内部构建的,所以我不确定这是否足以防止冲突。我发现了一些替代的 UUID 实现,它们被编写为 c 扩展,但由于 PyPy,我无法使用它们。

由于某些库的原因,我正在为这个项目使用 python,而且我在 python 中编码的经验很少。

更新:

现在我在分叉后添加了 seed(SystemRandom().getrandbits(128))。因此用 /dev/urandom 播种 PRNG。到目前为止,这似乎工作得很好。

我喜欢 rossum 提出的使用主进程 RNG 为子进程播种的想法。不过现在想来,我想用OS的RNG来做RNG的种子应该更稳妥吧。特别是,因为我的应用程序还在多个节点上分布式运行。恕我直言,使用 mac 地址和时间戳为初始 RNG 播种,然后使用 rossums 提案也应该有效。

发生这种情况是因为进程分叉导致每个进程都拥有相同内存的副本,包括相同 PRNG 状态的副本。相反,在使用 SystemRandom 的分叉后重新播种解决了这个问题,因为 SystemRandom 提供的随机数对于 操作系统 是全局的,而不是对单个进程。

请注意,以这种方式使用 random.random 仍然可能不安全。它使用相当弱的、可预测的用户空间 PRNG。我的第一个建议是简单地替换

import random

from random import SystemRandom
random = SystemRandom()

但如果仍然很慢,我确实制作了一个确定性用户空间 CSPRNG,您可以在此处尝试:https://pypi.org/project/streamrandom/

这甚至包括 UUID 生成功能,您可以像这样使用它:

import os
from streamrandom import StreamRandom, stream_from_seed

random = StreamRandom(stream_from_seed(os.random(128).decode("charmap")))
uuid = random.uuid4()