Python 超快生成大量随机数的方法

文章目录

      • 1.random.randint
      • 2./dev/urandom->os.urandom
      • 3.fastrand ×
      • 4.numpy
      • 5. AES CTR
    • 综合测试和总结

今天花费了很多时间在想办法提高Python的随机数生成的速度,因为我需要生成clickhouse的测试数据。
我每生成1亿行数据,每行包括2个随机的uint32,1个uint16,1个uint8和一个随机时间,花费的时间大约在60分钟左右。

1.random.randint

我最初生成uint32的代码长成这样:

import random
UINT32upBound=0xffffffff
for i in range(0,10**4):
    random.randint(0,UINT32upBound)

真的太慢了。

2./dev/urandom->os.urandom

然后我想到使用Linux系统的/dev/urandom,这是系统自带的随机数生成器,应该比较快。
参考网址:How to get numbers from /dev/random using Python? - Stack Overflow
随后我把代码修改了:

import os
for i in range(0,10**4):
    int(os.urandom(4).hex(),16)

确实有少量的提速,但不明显。
然后我看到问答:unix - Creating a large file of random bytes quickly - Super User
和:encryption - Fast Way to Randomize HD? - Unix & Linux Stack Exchange
里面提到/dev/urandom生成随机数的速度确实不够快,更快的方法是使用openssl的AES算法,但不能在Python中使用。

dd if=<(openssl enc -aes-256-ctr -pass pass:"$(dd if=/dev/urandom bs=128 count=1 2>/dev/null | base64)" -nosalt < /dev/zero) of=filename bs=1M count=100 iflag=fullblock

3.fastrand ×

然后我找到一个放在GitHub上的Python库:lemire/fastrand: Fast random number generation in Python (using PCG)
我尝试了,还是不够快。

4.numpy

随后我看到了另外一个回答:Efficient way to generate and use millions of random numbers in Python - Stack Overflow
里面提到了:random.random()只有300k/s,/dev/urandom有10M/S,如果使用numpy进行大规模生成,速度可以达到60M/S。
然后我就使用numpy:

import numpy as np
l= np.random.randint(low=-2147483648, high=0x7FFFFFFF, size=10**4)
for i in range(0,10**4):
    l[i]+2147483648

注意:np.random.random_integers方法被弃用了
这里说明一下,0x7FFFFFFF是int32的最大值,-2147483648是int32的最小值。
因为我需要uint32的随机数,但randint函数只能生成int32范围的随机数,所以在结果处加上int32的min值-2147483648。
上面的代码速度非常快。

5. AES CTR

另外还有提到,如果使用AES加密,生成随机数,速度也非常快。
使用AES算法的CTR模式生成随机数的原理是每次加密后count会自动加一,虽然加密同样的数据,但密文是不可预测的,随机的。
代码如下:

import os
from Crypto.Cipher import AES
from Crypto.Util import Counter
def AES_32_YIELD():
    key=os.urandom(32)
    aes = AES.new(key, AES.MODE_CTR, counter=Counter.new(128))
    data = "0" * 16
    data=data.encode('utf-8')
    while True:
        yield int(aes.encrypt(data).hex(),16)
yield_32=AES_32_YIELD()
for i in range(0,10**4):
    next(yield_32)

综合测试和总结

我这篇文章主要讨论的是随机数生成的速度问题,并没有涉及到安全问题,因为测试数据只要随机就可以了。
但如果要把随机数用于密码等方面,比较安全的做法是使用os.urandom


上面提到了那么多方法,总需要进行测试,然后比较速度,才让人知道哪个真的快。
Python中常用的测试方法是使用timeit这个库进行速度的测量,timeit是一个非常好用的库,用来测试代码运行速度的。
先放测试结果:

random.random()                         0.02370711136609316
np.random.rand()                        0.07226962689310312
********************
int(random.random()*0xffff)             0.051626757718622684
random.randint(0, 0xffff)               0.24031009431928396
int(os.urandom(4).hex(),16)             0.15973901469260454
hashlib.md5("md5 test".encode("utf-8")) 0.07776997052133083
np.random.randint(0xffff)               0.21123502869158983
Crypto.Random.random.getrandbits(32)    6.919489985331893
random.getrandbits(32)                  0.03729246836155653
********************
l.append(random.randint(0, 0xffff))     0.21211601048707962
np.random.randint(0xffff,size=count)    0.0007560765370726585
random.shuffle(a)                       0.14006372820585966

总结如下:生成单个0-1的随机数,使用random.random()就足够快了;
如果生成单个0-unsigned int的随机数,最快的是使用random.getrandbits(32),其次是int(random.random()*0xffff)
如果生成大批量0-unsigned int 32 的随机数,最快的方法是使用np.random.randint(0xffff,size=count)进行批量生成。

测试代码如下:

# -*- coding: utf-8 -*-
"""
Created on Wed Jul 17 19:57:55 2019

@author: sidanzhang
"""

import timeit
import random
import time
import datetime
import json
import hashlib
import os
from Crypto.Cipher import AES
from Crypto.Util import Counter
import Crypto.Random.random

import numpy as np

count = 10 ** 5


def func1():
    random.random()


def func2():
    int(random.random() * 0xffff)


def func3():
    random.randint(0, 0xffff)


def func4():
    int(os.urandom(4).hex(), 16)


def func5():
    hashlib.md5("md5 test".encode("utf-8"))


def func6():
    np.random.rand()


def func7():
    np.random.random_integers(0, 0xffff)


def func8():
    np.random.randint(0xffff)


def func9():
    global b, count
    b = np.random.randint(0xffff, size=count)


def func10():
    a = [i for i in range(0, count)]
    random.shuffle(a)


def func11():
    l = []
    for i in range(0, count):
        l.append(random.randint(0, 0xffff))


def func12():
    Crypto.Random.random.getrandbits(32)


def func13():
    random.getrandbits(32)


print('random.random()                        ', timeit.timeit(func1, number=count))
print('np.random.rand()                       ', timeit.timeit(func6, number=count))
print("*" * 20)
print('int(random.random()*0xffff)            ', timeit.timeit(func2, number=count))
print('random.randint(0, 0xffff)              ', timeit.timeit(func3, number=count))
print('int(os.urandom(4).hex(),16)            ', timeit.timeit(func4, number=count))
print('hashlib.md5("md5 test".encode("utf-8"))', timeit.timeit(func5, number=count))
print('np.random.randint(0xffff)              ', timeit.timeit(func8, number=count))
print('Crypto.Random.random.getrandbits(32)   ', timeit.timeit(func12, number=count))
print('random.getrandbits(32)                 ', timeit.timeit(func13, number=count))
print("*" * 20)
print('l.append(random.randint(0, 0xffff))    ', timeit.timeit(func11, number=1))
print('np.random.randint(0xffff,size=count)   ', timeit.timeit(func9, number=1))
print('random.shuffle(a)                      ', timeit.timeit(func10, number=1))

后记:
在我想办法提高Python生成随机数的速度时,另外一个提高速度的奇淫技巧是使用pypy替代Python。
pypy的速度是比Python快的。

参考文章:Slow and fast methods for generating random integers in Python - Eli Bendersky’s website
Generating Random Data in Python (Guide) – Real Python

你可能感兴趣的:(python,linux)