inmutables - manejo de memoria en python
¿Cuál es exactamente el punto de la vista de memoria en Python? (3)
Aquí está el código python3.
#!/usr/bin/env python3
import time
for n in (100000, 200000, 300000, 400000):
data = ''x''*n
start = time.time()
b = data
while b:
b = b[1:]
print (''bytes {:d} {:f}''.format(n,time.time()-start))
for n in (100000, 200000, 300000, 400000):
data = b''x''*n
start = time.time()
b = memoryview(data)
while b:
b = b[1:]
print (''memview {:d} {:f}''.format(n,time.time()-start))
Comprobando la documentation en memoryview:
Los objetos memoryview permiten que el código Python acceda a los datos internos de un objeto que admite el protocolo de búfer sin copiar.
clase memoryview (obj)
Crear una vista de memoria que haga referencia a obj. obj debe soportar el protocolo buffer. Los objetos incorporados que admiten el protocolo de búfer incluyen bytes y bytearray.
Entonces nos dan el código de muestra:
>>> v = memoryview(b''abcefg'')
>>> v[1]
98
>>> v[-1]
103
>>> v[1:4]
<memory at 0x7f3ddc9f4350>
>>> bytes(v[1:4])
b''bce''
Una cita más, ahora vamos a echar un vistazo más de cerca:
>>> b = b''long bytes stream''
>>> b.startswith(b''long'')
True
>>> v = memoryview(b)
>>> vsub = v[5:]
>>> vsub.startswith(b''bytes'')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: ''memoryview'' object has no attribute ''startswith''
>>> bytes(vsub).startswith(b''bytes'')
True
>>>
Así que lo que recojo de lo anterior:
Creamos un objeto de vista de memoria para exponer los datos internos de un objeto de búfer sin copiar, sin embargo, para hacer algo útil con el objeto (llamando a los métodos proporcionados por el objeto), ¡tenemos que crear una copia!
Por lo general, la memoria de memoria (o el objeto del búfer anterior) se necesitaría cuando tenemos un objeto grande, y los cortes también pueden ser grandes. La necesidad de una mayor eficiencia estaría presente si estuviéramos haciendo rebanadas grandes, o si hiciéramos rebanadas pequeñas pero muchas veces.
Con el esquema anterior, no veo cómo puede ser útil para cualquiera de las situaciones, a menos que alguien pueda explicarme lo que me estoy perdiendo aquí.
Edit1:
Tenemos una gran cantidad de datos, queremos procesarlos avanzando a través de ella de principio a fin, por ejemplo, extrayendo tokens desde el inicio de un búfer de cadena hasta que el búfer se consume. búfer, y el puntero se puede pasar a cualquier función que espere el tipo de búfer. ¿Cómo se puede hacer algo similar en python?
La gente sugiere soluciones alternativas, por ejemplo, muchas funciones de cadenas y expresiones regulares toman argumentos de posición que pueden usarse para emular el avance de un puntero. Hay dos problemas con esto: primero es una solución, se ve obligado a cambiar su estilo de codificación para superar las deficiencias, y segundo: no todas las funciones tienen argumentos de posición, por ejemplo, las funciones de startswith
regulares y startswith
do, encode()
/ decode()
no
Otros pueden sugerir cargar los datos en trozos, o procesar el búfer en pequeños segmentos más grandes que el token máximo. Bien, estamos conscientes de estas posibles soluciones, pero se supone que trabajamos de una manera más natural en Python sin tratar de doblar el estilo de codificación para que se ajuste al idioma, ¿no es así?
Edit2:
Un ejemplo de código aclararía las cosas. Esto es lo que quiero hacer, y lo que asumí que la vista de memoria me permitiría hacer a primera vista. Permite usar pmview (vista de memoria adecuada) para la funcionalidad que estoy buscando:
tokens = []
xlarge_str = get_string()
xlarge_str_view = pmview(xlarge_str)
while True:
token = get_token(xlarge_str_view)
if token:
xlarge_str_view = xlarge_str_view.vslice(len(token))
# vslice: view slice: default stop paramter at end of buffer
tokens.append(token)
else:
break
Una razón por la que las memoryviews
son útiles es porque se pueden memoryviews
sin copiar los datos subyacentes, a diferencia de los bytes
/ str
.
Por ejemplo, tomemos el siguiente ejemplo de juguete.
import time
for n in (100000, 200000, 300000, 400000):
data = ''x''*n
start = time.time()
b = data
while b:
b = b[1:]
print ''bytes'', n, time.time()-start
for n in (100000, 200000, 300000, 400000):
data = ''x''*n
start = time.time()
b = memoryview(data)
while b:
b = b[1:]
print ''memoryview'', n, time.time()-start
En mi computadora, me sale
bytes 100000 0.200068950653
bytes 200000 0.938908100128
bytes 300000 2.30898690224
bytes 400000 4.27718806267
memoryview 100000 0.0100269317627
memoryview 200000 0.0208270549774
memoryview 300000 0.0303030014038
memoryview 400000 0.0403470993042
Puede ver claramente la complejidad cuadrática del corte de cadena repetido. Incluso con solo 400000 iteraciones, ya es inmutable. Mientras tanto, la versión memoryview tiene una complejidad lineal y es muy rápida.
Edición: Tenga en cuenta que esto se hizo en CPython. Hubo un error en Pypy hasta 4.0.1 que hacía que las vistas de memoria tuvieran un rendimiento cuadrático.
memoryview
objetos de vista de memoryview
son excelentes cuando necesita subconjuntos de datos binarios que solo necesitan ser compatibles con la indexación. En lugar de tener que tomar porciones (y crear objetos nuevos, potencialmente grandes) para pasar a otra API , puede tomar un objeto de vista de memoryview
.
Un ejemplo de API tal sería el módulo de struct
. En lugar de pasar una porción del objeto de bytes
grandes para analizar los valores C empaquetados, se pasa una memoryview
de memoryview
de la región de la que se deben extraer los valores.
memoryview
objetos memoryview
, de hecho, soportan la struct
desempaquetando de forma nativa; puede apuntar a una región del objeto de bytes
subyacente con una porción, luego usar .cast()
para "interpretar" los bytes subyacentes como enteros largos, o valores de coma flotante, o listas de enteros de n dimensiones. Esto hace que para interpretaciones de formato de archivo binario muy eficientes, sin tener que crear más copias de los bytes.