Jaki jest dokładnie sens przeglądania pamięci w Pythonie

86

Sprawdzanie dokumentacji dotyczącej podglądu pamięci:

obiekty memoryview umożliwiają kodowi Python dostęp do wewnętrznych danych obiektu, który obsługuje protokół bufora, bez kopiowania.

klasa memoryview (obj)

Utwórz widok pamięci, który odwołuje się do obj. obj musi obsługiwać protokół bufora. Wbudowane obiekty, które obsługują protokół bufora, obejmują bajty i bajt.

Następnie otrzymujemy przykładowy kod:

>>> v = memoryview(b'abcefg')
>>> v[1]
98
>>> v[-1]
103
>>> v[1:4]
<memory at 0x7f3ddc9f4350>
>>> bytes(v[1:4])
b'bce'

Cytat zakończony, teraz przyjrzyjmy się bliżej:

>>> 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
>>> 

Więc co z powyższego wyciągam:

Tworzymy obiekt memoryview, aby ujawnić wewnętrzne dane obiektu buforowego bez kopiowania, jednak aby zrobić cokolwiek pożytecznego z obiektem (przez wywołanie metod dostarczonych przez obiekt), musimy utworzyć kopię!

Zwykle memoryview (lub stary obiekt bufora) byłby potrzebny, gdy mamy duży obiekt, a plasterki również mogą być duże. Potrzeba lepszej wydajności byłaby obecna, gdybyśmy robili duże plasterki lub robili małe plasterki, ale dużą liczbę razy.

Z powyższym schematem nie widzę, jak może być przydatny w obu sytuacjach, chyba że ktoś może mi wyjaśnić, czego tutaj brakuje.

Edycja1:

Mamy dużą porcję danych, chcemy ją przetworzyć, przechodząc przez nią od początku do końca, na przykład wyodrębniając tokeny od początku bufora ciągów do momentu zużycia bufora. W języku C oznacza to przesunięcie wskaźnika przez bufor, a wskaźnik można przekazać do dowolnej funkcji, która oczekuje typu buforu. Jak można zrobić coś podobnego w Pythonie?

Ludzie sugerują obejście tego problemu, na przykład wiele funkcji ciągów i wyrażeń regularnych przyjmuje argumenty pozycji, które mogą być używane do emulacji przesuwania wskaźnika. Są z tym dwa problemy: po pierwsze jest to obejście, jesteś zmuszony zmienić styl kodowania, aby przezwyciężyć niedociągnięcia, a po drugie: nie wszystkie funkcje mają argumenty pozycji, na przykład funkcje regex i startswithwykonaj, encode()/ decode()nie.

Inni mogą sugerować ładowanie danych porcjami lub przetwarzanie buforu w małych segmentach większych niż maksymalny token. OK, więc jesteśmy świadomi tych możliwych obejść, ale powinniśmy pracować w Pythonie w bardziej naturalny sposób, bez próby naginania stylu kodowania, aby pasował do języka - czyż nie?

Edit2:

Przykładowy kod sprawiłby, że wszystko było jaśniejsze. To jest to, co chcę zrobić i na pierwszy rzut oka założyłem, że widok wspomnień pozwoli mi zrobić. Użyjmy pmview (właściwego widoku pamięci) dla funkcji, której szukam:

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
Basel Shishani
źródło
9
Odpowiedź w przywoływanym pytaniu nie zawiera szczegółów. Pytanie nie dotyczy również potencjalnych problemów z punktu widzenia ucznia.
Basel Shishani,

Odpowiedzi:

83

Jednym z powodów, memoryviewdla których są przydatne, jest to, że można je kroić bez kopiowania danych źródłowych, w przeciwieństwie do bytes/ str.

Na przykład weźmy następujący przykład zabawki.

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

Na moim komputerze otrzymuję

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

Wyraźnie widać kwadratową złożoność powtarzanego cięcia struny. Nawet przy zaledwie 400000 iteracjach jest już niezmienialny. Tymczasem wersja pamięci ma liniową złożoność i jest błyskawiczna.

Edycja: Zauważ, że zostało to zrobione w CPythonie. Wystąpił błąd w Pypy do wersji 4.0.1, który powodował, że podglądy pamięci miały wydajność kwadratową.

Antymon
źródło
Przykład nie działa w Pythonie 3TypeError: memoryview: a bytes-like object is required, not 'str'
Calculus
@Jose printjako instrukcja również nie działa w Pythonie 3. Ten kod został napisany dla Pythona 2, chociaż zmiany wymagane w Pythonie 3 są dość trywialne.
Antymon
@Yumi Tada, strw python3 jest zupełnie inaczej zdefiniowane w python2.
hcnhcn012
5
Ta odpowiedź nie odnosi się do faktu, że aby zrobić cokolwiek "użytecznego", ponieważ pytający twierdzi, że musisz użyć bajtów (), które
kopiują
1
@ citizen2077 Jak pokazuje mój przykład, jest to przydatne do wydajnego wykonywania manipulacji pośrednich, nawet jeśli ostatecznie skopiujesz go do obiektu bajtów.
Antymon
59

memoryviewobiekty są świetne, gdy potrzebujesz podzbiorów danych binarnych, które muszą obsługiwać tylko indeksowanie. Zamiast pobierać plasterki (i tworzyć nowe, potencjalnie duże) obiekty, aby przekazać je do innego interfejsu API , możesz po prostu wziąć memoryviewobiekt.

Jednym z takich przykładów API byłby structmoduł. Zamiast przekazywać wycinek dużego bytesobiektu w celu przeanalizowania upakowanych wartości C, należy przekazać memoryviewtylko obszar, z którego należy wyodrębnić wartości.

memoryviewobiekty w rzeczywistości obsługują structrozpakowywanie natywnie; możesz wycelować w region bytesobiektu bazowego za pomocą wycinka, a następnie użyć go .cast()do „interpretacji” bazowych bajtów jako długie liczby całkowite, wartości zmiennoprzecinkowe lub n-wymiarowe listy liczb całkowitych. Zapewnia to bardzo wydajną interpretację formatu plików binarnych, bez konieczności tworzenia większej liczby kopii bajtów.

Martijn Pieters
źródło
1
A co robisz, gdy potrzebujesz podzbiorów, które obsługują więcej niż indeksowanie ?!
Basel Shishani,
2
@BaselShishani: nie używaj pliku memoryview. Masz do czynienia z tekstem, a nie z danymi binarnymi.
Martijn Pieters
Tak, zajmowanie się tekstem. Więc nie używamy widoku pamięci, czy istnieje alternatywa?
Basel Shishani,
Jaki problem próbujesz rozwiązać? Czy podciągi, które musisz przetestować, są tak duże?
Martijn Pieters
6
@BaselShishani: wycinanie widoku pamięci zwraca nowy widok pamięci obejmujący tylko ten region.
Martijn Pieters
5

Pozwólcie, że wyjaśnię, na czym polega błąd w zrozumieniu.

Pytający, podobnie jak ja, oczekiwał, że będzie w stanie stworzyć widok pamięci, który wybierze wycinek istniejącej tablicy (na przykład bajty lub bajt). Dlatego spodziewaliśmy się czegoś takiego:

desired_slice_view = memoryview(existing_array, start_index, end_index)

Niestety, nie ma takiego konstruktora, a dokumentacja nie określa, co należy zrobić.

Kluczem jest to, że musisz najpierw zrobić podgląd pamięci, który obejmuje całą istniejącą tablicę. Z tego widoku pamięci można utworzyć drugi podgląd pamięci, który obejmuje wycinek istniejącej tablicy, na przykład:

whole_view = memoryview(existing_array)
desired_slice_view = whole_view[10:20]

Krótko mówiąc, celem pierwszej linii jest po prostu dostarczenie obiektu, którego implementacja plastra (dunder-getitem) zwraca widok pamięci.

To może wydawać się nieporządne, ale można to zracjonalizować na kilka sposobów:

  1. Naszym pożądanym wynikiem jest widok pamięci, który jest kawałkiem czegoś. Zwykle otrzymujemy obiekt pokrojony w plasterki z obiektu tego samego typu, używając na nim operatora wycinka [10:20]. Jest więc jakiś powód, by oczekiwać, że musimy uzyskać nasz pożądany_widok_slice z widoku pamięci i dlatego pierwszym krokiem jest uzyskanie obrazu pamięci całej podstawowej tablicy.

  2. Naiwne oczekiwanie konstruktora Memoryview z argumentami początkowymi i końcowymi nie uwzględnia faktu, że specyfikacja wycinka naprawdę potrzebuje całej ekspresji zwykłego operatora wycinka (włączając takie rzeczy jak [3 :: 2] lub [: -4] itd.). Nie ma sposobu, aby po prostu użyć istniejącego (i zrozumiałego) operatora w tym konstruktorze jednowierszowym. Nie możesz dołączyć go do argumentu existing_array, ponieważ spowoduje to utworzenie wycinka tej tablicy, zamiast przekazywania konstruktorowi memoryview niektórych parametrów wycinka. I nie możesz użyć samego operatora jako argumentu, ponieważ jest to operator, a nie wartość lub obiekt.

Można sobie wyobrazić, że konstruktor Memoryview mógłby wziąć obiekt wycinka:

desired_slice_view = memoryview(existing_array, slice(1, 5, 2) )

... ale to nie jest zbyt satysfakcjonujące, ponieważ użytkownicy musieliby dowiedzieć się o obiekcie plasterka i co oznaczają jego parametry konstruktora, kiedy już myślą w kategoriach notacji operatora wycinka.

gwideman
źródło
4

Oto kod 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))
jimaf
źródło
1

Doskonały przykład autorstwa Antymona. Właściwie w Pythonie3 możesz zamienić dane = 'x' * n na dane = bajty (n) i umieścić nawiasy na wypisanych instrukcjach, jak poniżej:

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)
user2494386
źródło