Подтвердить что ты не робот

Эффективный способ создания большого случайного bytearray

Мне нужно создать большой внешний вид определенного размера, но размер не известен до времени выполнения. Байты должны быть довольно случайными. Размер байтов может быть не меньше, чем несколько килобайт, но не более нескольких МБ. Я не хочу повторять байты за байтом. Это слишком медленно - мне нужна производительность, похожая на numpy.random. Однако у меня нет модуля numpy для этого проекта. Есть ли что-то часть стандартной установки python, которая сделает это? Или мне нужно скомпилировать мою собственную, используя C?

для тех, кто запрашивает тайминги:

>>> timeit.timeit('[random.randint(0,128) for i in xrange(1,100000)]',setup='import random', number=100)
35.73110193696641
>>> timeit.timeit('numpy.random.random_integers(0,128,100000)',setup='import numpy', number=100)
0.5785652013481126
>>> 
4b9b3361

Ответ 1

Модуль os предоставляет urandom даже в Windows:

bytearray(os.urandom(1000000))

Кажется, что это работает так быстро, как вам нужно, на самом деле, я получаю лучшие тайминги, чем ваш numpy (хотя наши машины могут быть совершенно разными):

timeit.timeit(lambda:bytearray(os.urandom(1000000)), number=10)
0.0554857286941

Ответ 2

Что не так, просто включив numpy? Во всяком случае, это создает случайное N-разрядное целое число:

import random
N = 100000
bits = random.getrandbits(N)

Итак, если вам нужно установить, установлено ли значение j-го бита или нет, вы можете сделать bits & (2**j)==(2**j)

EDIT: он попросил массив байтов не бит-массив. Нед лучше: your_byte_array= bytearray((random.getrandbits(8) for i in xrange(N))

Ответ 3

import random
def randbytes(n):
    for _ in xrange(n):
        yield random.getrandbits(8)

my_random_bytes = bytearray(randbytes(1000000))

Возможно, что-то в itertools, которое может помочь здесь, всегда есть...

Мои тайминги показывают, что это происходит примерно в пять раз быстрее, чем [random.randint(0,128) for i in xrange(1,100000)]

Ответ 4

Существует несколько возможностей, несколько быстрее, чем os.urandom. Также подумайте, должны ли данные генерироваться детерминистически из случайного семени. Это бесценно для модульных тестов, где отказы должны быть воспроизводимыми.

короткое и содержательное:

lambda n:bytearray(map(random.getrandbits,(8,)*n))

Я использовал выше для модульных тестов, и это было достаточно быстро, но можно ли это сделать быстрее?

с помощью itertools:

lambda n:bytearray(itertools.imap(random.getrandbits,itertools.repeat(8,n))))

itertools и struct, производящие 8 байтов на итерацию

lambda n:(b''.join(map(struct.Struct("!Q").pack,itertools.imap(
    random.getrandbits,itertools.repeat(64,(n+7)//8)))))[:n]

Все, что основано на b''.join, заполнит 3-7x память, потребляемую конечным bytearray, временными объектами, так как она приостанавливает все подстроки перед их объединением, а объекты python имеют много накладных расходов на хранение.

Изготовление больших кусков со специальной функцией обеспечивает лучшую производительность и позволяет избежать заполнения памяти.

import random,itertools,struct,operator
def randbytes(n,_struct8k=struct.Struct("!1000Q").pack_into):
    if n<8000:
        longs=(n+7)//8
        return struct.pack("!%iQ"%longs,*map(
            random.getrandbits,itertools.repeat(64,longs)))[:n]
    data=bytearray(n);
    for offset in xrange(0,n-7999,8000):
        _struct8k(data,offset,
            *map(random.getrandbits,itertools.repeat(64,1000)))
    offset+=8000
    data[offset:]=randbytes(n-offset)
    return data

Производительность

  • .84 МБ/с: оригинальное решение с randint:
  • 4.8 MB/s: bytearray(getrandbits(8) for _ in xrange(n)): (решение другого плаката)
  • 6,4 МБ/с: bytearray(map(getrandbits,(8,)*n))
  • 7.2 МБ/с: itertools и getrandbits
  • 10 МБ/с: os.urandom
  • 23 МБ/с: itertools и struct
  • 35 МБ/с: оптимизированная функция (выполняется для len = 100MB... 1KB)

Примечание: все тесты использовали 10KB в качестве размера строки. Результаты были согласованы до заполнения промежуточных результатов.

Примечание. os.urandom предназначен для обеспечения безопасных случайных семян. Приложения расширяют это семя со своим собственным быстрым PRNG. Вот пример использования AES в режиме счетчика как PRNG:

import os
seed=os.urandom(32)

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
backend = default_backend()
cipher = Cipher(algorithms.AES(seed), modes.CTR(b'\0'*16), backend=backend)
encryptor = cipher.encryptor()

nulls=b'\0'*(10**5) #100k
from timeit import timeit
t=timeit(lambda:encryptor.update(nulls),number=10**5) #1GB, (100K*10k)
print("%.1f MB/s"%(1000/t))

Это создает псевдослучайные данные при 180 МБ/с. (без аппаратного ускорения AES, одноядерного ядра) Это только ~ 5x скорость чистого кода python выше.

Добавление

Там есть чистая криптографическая библиотека питона, ожидающая записи. Использование этих методов вместе с методами hashlib и потокового шифрования выглядит многообещающим. Здесь тизер, быстрая строка xor (42 МБ/с).

def xor(a,b):
    s="!%iQ%iB"%divmod(len(a),8)
    return struct.pack(s,*itertools.imap(operator.xor,
        struct.unpack(s,a),
        struct.unpack(s,b)))