Что такое точка памяти в Python
Проверка документации по памяти:
объекты memoryview позволяют коду Python получать доступ к внутренним данным объект, который поддерживает буферный протокол без копирования.
класс memoryview (obj)
Создайте представление памяти, которое ссылается на obj. obj должен поддерживать буферный протокол. Встроенные объекты, поддерживающие буферный протокол включают байты и bytearray.
Затем нам предоставляется пример кода:
>>> v = memoryview(b'abcefg')
>>> v[1]
98
>>> v[-1]
103
>>> v[1:4]
<memory at 0x7f3ddc9f4350>
>>> bytes(v[1:4])
b'bce'
Цитата, теперь давайте поближе рассмотрим:
>>> 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
>>>
Итак, что я вижу из вышеперечисленного:
Мы создаем объект memoryview для отображения внутренних данных объекта буфера без
копирование, однако, для того, чтобы делать что-либо полезное с объектом (путем вызова методов
предоставляемый объектом), мы должны создать копию!
Обычно память (или старый буферный объект) понадобится, когда у нас есть большой объект,
и срезы также могут быть большими. Потребность в лучшей эффективности будет присутствовать
если мы делаем большие ломтики или делаем небольшие кусочки, но много раз.
С приведенной выше схемой я не вижу, как это может быть полезно для любой ситуации, если только
кто-то может объяснить мне, что мне здесь не хватает.
Edit1:
У нас есть большой кусок данных, мы хотим его обработать, продвигая его от начала до
конец, например, извлечение токенов с начала буфера строки до тех пор, пока буфер не будет потреблен. В термине C это продвигает указатель через буфер, и указатель может быть передан
к любой функции, ожидающей тип буфера. Как можно что-то подобное сделать в python?
Люди предлагают обходные пути, например, многие функции строк и регулярных выражений занимают позицию
аргументы, которые могут использоваться для подражания продвижению указателя. В этом есть две проблемы: первая
это работа, вы вынуждены изменить свой стиль кодирования, чтобы преодолеть недостатки, и
second: не все функции имеют аргументы позиции, например, функции регулярных выражений и startswith
do, encode()
/decode()
do not.
Другие могут предлагать загружать данные в куски или обрабатывать буфер в небольших
сегментов, превышающих максимальный токен. Хорошо, поэтому мы знаем об этих возможностях
обходные пути, но мы должны работать более естественным образом в python без
пытаясь согнуть стиль кодирования, чтобы он соответствовал языку - не так ли?
Edit2:
Образец кода упростит ситуацию. Это то, что я хочу сделать, и то, что я предположил, будет иметь вид памяти, позволит мне сделать на первый взгляд. Позволяет использовать pmview (правильный вид памяти) для функциональности, которую я ищу:
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
Ответы
Ответ 1
Одна из причин memoryviews
полезна в том, что они могут быть разрезаны без копирования базовых данных, в отличие от bytes
/str
.
Например, возьмите следующий пример игрушки.
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
На моем компьютере я получаю
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
Вы можете четко видеть квадратичную сложность повторной нарезки строк. Даже с 400000 итераций он уже не может быть изменен. Между тем, версия памяти имеет линейную сложность и молниеносно.
Изменить: обратите внимание, что это было сделано в CPython. Была ошибка в Pypy до 4.0.1, которая привела к тому, что память имеет квадратичную производительность.
Ответ 2
memoryview
объекты великолепны, когда вам нужны подмножества двоичных данных, которые должны поддерживать индексирование. Вместо того, чтобы принимать фрагменты (и создавать новые, потенциально большие) объекты для перехода к другому API, вы можете просто взять объект memoryview
.
Одним из таких примеров API будет модуль struct
. Вместо того, чтобы передавать в срез большого объекта bytes
для анализа значений упакованного значения C, вы передаете memoryview
только область, в которой вам нужно извлечь значения из.
memoryview
объекты, по сути, поддерживают struct
распаковку; вы можете направить область базового объекта bytes
на срез, а затем использовать .cast()
для "интерпретации" базовых байтов как длинных целых чисел, или значений с плавающей запятой, или n-мерных списков целых чисел. Это обеспечивает очень эффективную интерпретацию формата двоичного файла, без необходимости создавать больше копий байтов.
Ответ 3
Вот код Python3.
#!/usr/bin/env python3
import time
for n in (100000, 200000, 300000, 400000):
data = b'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))
Ответ 4
Отличный пример сурьмы.
На самом деле, в Python3 вы можете заменить data = 'x' * n на data = bytes (n) и поместить скобки в операторы вывода, как показано ниже:
import time
for n in (100000, 200000, 300000, 400000):
#data = 'x'*n
data = bytes(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
data = bytes(n)
start = time.time()
b = memoryview(data)
while b:
b = b[1:]
print('memoryview', n, time.time()-start)
Ответ 5
Позвольте мне пояснить, в чем здесь проблема в понимании.
Спрашивающий, как и я, ожидал, что сможет создать представление памяти, которое выбирает фрагмент существующего массива (например, байты или байтовый массив). Поэтому мы ожидали что-то вроде:
desired_slice_view = memoryview(existing_array, start_index, end_index)
Увы, такого конструктора не существует, и в документах не указано, что делать вместо этого.
Ключ заключается в том, что вы должны сначала сделать просмотр памяти, который покрывает весь существующий массив. Из этого обзора памяти вы можете создать второй просмотр памяти, который покрывает фрагмент существующего массива, например:
whole_view = memoryview(existing_array)
desired_slice_view = whole_view[10:20]
Короче говоря, цель первой строки - просто предоставить объект, реализация слайса которого (dunder-getitem) возвращает представление памяти.
Это может показаться неопрятным, но это можно объяснить несколькими способами:
Наш желаемый результат - это просмотр памяти, который является частью чего-то. Обычно мы получаем нарезанный объект от объекта того же типа, используя оператор слайса [10:20]. Таким образом, есть основания ожидать, что нам нужно получить требуемый_слайс_вью из обзора памяти и, следовательно, первым шагом является получение обзора памяти всего базового массива.
Наивное ожидание конструктора вида памяти с аргументами start и end не учитывает, что спецификации слайса действительно нужна вся выразительность обычного оператора слайса (включая такие вещи, как [3 :: 2] или [: -4] и т.д.). Невозможно просто использовать существующий (и понятный) оператор в этом однострочном конструкторе. Вы не можете присоединить его к аргументу Существующий_аррэй, так как это сделает срез этого массива вместо того, чтобы сообщать конструктору памяти некоторые параметры среза. И вы не можете использовать сам оператор в качестве аргумента, потому что это оператор, а не значение или объект.
Возможно, конструктор вида памяти мог бы взять объект слайса:
desired_slice_view = memoryview(existing_array, slice(1, 5, 2) )
... но это не очень удовлетворительно, поскольку пользователям придется узнавать об объекте среза и о том, что означают его параметры конструктора, когда они уже думают в терминах нотации оператора среза.