2017-10-03 92 views
8

Mam obiekt" bajtów "i" int "maski, chcę zrobić xor na wszystkie bajty z moją maską. Wykonuję tę akcję wielokrotnie nad dużymi obiektami "bajtowymi" (~ 4096 KB).Python - "xorowanie każdego bajtu w" bajtach "w najbardziej efektywny sposób

Jest to kod mam, które działa dobrze, tylko jest to bardzo obciąża CPU i spowalnia mój skrypt:

# 'data' is bytes and 'mask' is int 
bmask = struct.pack('!I', mask) # converting the "int" mask to "bytes" of 4 bytes 
a = bytes(b^m for b, m in zip(data, itertools.cycle(bmask))) 

Najlepszym mogłem wymyślić jest ten, który jest około 20 razy szybciej:

# 'data' is bytes and 'mask' is int 
# reversing the bytes of the mask 
bmask = struct.pack("<I", mask) 
mask = struct.unpack(">I", bmask)[0] 

# converting from bytes to array of "int"s 
arr = array.array("I", data) 

# looping over the "int"s 
for i in range(len(arr)): 
    arr[i] ^= mask 

# must return bytes 
a = bytes(arr) 

moje pytania są następujące:

  1. Czy jest bardziej efektywny sposób to zrobić (CPU-Wize)?
  2. Czy istnieje "czystszy" sposób, aby to zrobić (bez ranienia wydajności)?

P.S. jeśli to ma jakiekolwiek znaczenie, używam Python 3.5

+0

Co jest 'data'? Czy jest to lista, bajty, iteratory czy ...? –

+0

Jeśli jest wąskim gardłem, to może mieć sens, aby napisać funkcję o nazwie C od Python –

+0

„dane” jest bajtów, będę aktualizować pytanie –

Odpowiedz

3

Nie sądzę, można uzyskać znacznie szybciej niż algorytm, za pomocą czystego Pythona. (Ale odpowiedź Fabio Veronese pokazuje, że to nieprawda). Możesz zgolić trochę czasu, wykonując pętlę w zrozumieniu listy, ale wtedy ta lista musi zostać przekonwertowana z powrotem do tablicy, a tablica musi zostać przekonwertowana na bajty, więc używa więcej pamięci RAM dla nieznacznej korzyści .

Można jednak przyspieszyć ten , używając polecenia Numpy. Oto krótkie demo.

from time import perf_counter 
from random import randrange, seed 
import array 
import numpy as np 

seed(42) 

def timed(func): 
    ''' Timing decorator ''' 
    def wrapped(*args): 
     start = perf_counter() 
     result = func(*args) 
     stop = perf_counter() 
     print('{}: {:.6f} seconds'.format(func.__name__, stop - start)) 
     return result 
    wrapped.__name__ = func.__name__ 
    wrapped.__doc__ = func.__doc__ 
    return wrapped 

@timed 
def do_mask_arr1(data, mask): 
    arr = array.array("I", data) 
    # looping over the "int"s 
    for i in range(len(arr)): 
     arr[i] ^= mask 
    return arr.tobytes() 

@timed 
def do_mask_arr2(data, mask): 
    arr = array.array("I", data) 
    return array.array("I", [u^mask for u in arr]).tobytes() 

@timed 
def do_mask_numpy(data, mask): 
    return (np.fromstring(data, dtype=np.uint32)^mask).tobytes() 

@timed 
def make_data(datasize): 
    ''' Make some random bytes ''' 
    return bytes(randrange(256) for _ in range(datasize)) 

datasize = 100000 
mask = 0x12345678 
data = make_data(datasize) 

d1 = do_mask_arr1(data, mask) 
d2 = do_mask_arr2(data, mask) 
print(d1 == d2) 

d3 = do_mask_numpy(data, mask) 
print(d1 == d3) 

typowe wyjście

make_data: 0.751557 seconds 
do_mask_arr1: 0.026865 seconds 
do_mask_arr2: 0.025110 seconds 
True 
do_mask_numpy: 0.000438 seconds 
True 

Testowane przy użyciu Python 3.6.0 na starej maszynie pojedynczy rdzeń 32 bit 2GHz z systemem Linux.

Po prostu wykonałem bieg z datasize = 4000000, a do_mask_numpy zajął 0,0422 sekundy.

+0

Właściwie jest to możliwe, aby osiągnąć lepszy wynik nadal przy użyciu Pythona, byle numpy pozostaje najszybszy. –

2

Alternatywa w przypadku, gdy nie chcesz używać numpy. Zaletą jest wykonanie pojedynczego porównania przy jednoczesnym zwiększeniu rozmiaru maski do potrzebnego (w zależności od danych).

@timed 
def do_mask_int(data, mask): 
    intdata = int.from_bytes(data, byteorder='little', signed=False) 
    strmask = format(mask,'0x') 
    strmask = strmask * ((intdata.bit_length() + 31) // 32) 
    n = intdata^int(strmask, 16) 
    return n.to_bytes(((n.bit_length() + 7) // 8), 'little') or b'\0' 

Wyniki są jak następuje:

make_data: 8.288754 seconds 
do_mask_arr1: 0.258530 seconds 
do_mask_arr2: 0.253095 seconds 
True 
do_mask_numpy: 0.010309 seconds 
True 
do_mask_int: 0.060408 seconds 
True 

Wciąż kredytów do NumPy za to szybciej, ale może ktoś nie chce, aby uwzględnić go w środowisku produkcyjnym.

:] Najlepszy

+0

Bardzo sprytny!Możesz być w stanie zrobić to szybciej, zastępując te dywizje zmianami: 'a // 32' ->' a >> 5', 'a // 8' ->' a >> 3' –

+0

niewiele tak naprawdę 'make_data: 8.696361 sekund do_mask_arr1: 0.260604 sekund do_mask_arr2: 0.207433 sekund prawda do_mask_numpy: 0.006003 sekund Prawdziwe do_mask_int: 0.050470 sekund true' –