Development Tip

Python에서 memoryview의 요점은 정확히 무엇입니까

yourdevel 2020. 11. 21. 09:11
반응형

Python에서 memoryview의 요점은 정확히 무엇입니까


memoryview에 대한 문서 확인 :

memoryview 객체를 사용하면 Python 코드가 복사없이 버퍼 프로토콜을 지원하는 객체의 내부 데이터에 액세스 할 수 있습니다.

클래스 memoryview (obj)

obj를 참조하는 memoryview를 만듭니다. obj는 버퍼 프로토콜을 지원해야합니다. 버퍼 프로토콜을 지원하는 기본 제공 개체에는 바이트 및 바이트 배열이 포함됩니다.

그런 다음 샘플 코드가 제공됩니다.

>>> 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 객체를 생성합니다. 그러나 객체에 대해 유용한 작업을 수행하려면 (객체가 제공하는 메소드를 호출하여) 사본을 생성해야합니다!

일반적으로 메모리 뷰 (또는 이전 버퍼 객체)는 큰 객체가있을 때 필요하며 슬라이스도 클 수 있습니다. 큰 조각을 만들거나 작은 조각을 여러 번 만들면 더 나은 효율성이 필요합니다.

위의 계획을 사용하면 누군가 내가 여기서 놓친 것을 설명 할 수 없다면 어떤 상황에서도 유용 할 수 있는지 알 수 없습니다.

편집 1 :

우리는 많은 양의 데이터를 가지고 있으며, 예를 들어 버퍼가 소모 될 때까지 문자열 버퍼의 시작 부분에서 토큰을 추출하는 것과 같이 처음부터 끝까지 진행하여 처리하려고합니다 .C 용어에서 이것은 포인터를 통해 포인터를 이동하는 것입니다. 버퍼 및 포인터는 버퍼 유형을 예상하는 모든 함수에 전달할 수 있습니다. 파이썬에서 비슷한 일을 어떻게 할 수 있습니까?

사람들은 해결 방법을 제안합니다. 예를 들어 많은 문자열 및 정규식 함수가 포인터 전진을 에뮬레이트하는 데 사용할 수있는 위치 인수를 사용합니다. 여기에는 두 가지 문제가 있습니다. 첫 번째는 해결 방법이며 단점을 극복하기 위해 코딩 스타일을 변경해야합니다. 두 번째 : 모든 함수에 위치 인수가있는 것은 아닙니다 (예 : regex 함수 및 startswith수행 encode()/ decode()하지 않음).

다른 사람들은 데이터를 청크로로드하거나 최대 토큰보다 큰 작은 세그먼트로 버퍼를 처리하도록 제안 할 수 있습니다. 좋아, 우리는 이러한 가능한 해결 방법을 알고 있지만, 언어에 맞게 코딩 스타일을 구부리지 않고 파이썬에서 더 자연스러운 방식으로 작업해야합니다. 그렇지 않습니까?

편집 2 :

코드 샘플은 상황을 더 명확하게합니다. 이것이 제가하고 싶은 일이며, memoryview가 언뜻보기에 할 수있는 일이라고 생각했습니다. 내가 찾고있는 기능에 대해 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

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

반복되는 스트링 슬라이싱의 2 차 복잡성을 명확하게 볼 수 있습니다. 400000 회만 반복해도 이미 관리가 불가능합니다. 한편, memoryview 버전은 선형 복잡성을 가지며 번개처럼 빠릅니다.

편집 : 이것은 CPython에서 수행되었습니다. 4.0.1까지 Pypy에 버그가있어서 memoryview가 2 차 성능을 갖게되었습니다.


memoryview objects are great when you need subsets of binary data that only need to support indexing. Instead of having to take slices (and create new, potentially large) objects to pass to another API you can just take a memoryview object.

One such API example would be the struct module. Instead of passing in a slice of the large bytes object to parse out packed C values, you pass in a memoryview of just the region you need to extract values from.

memoryview objects, in fact, support struct unpacking natively; you can target a region of the underlying bytes object with a slice, then use .cast() to 'interpret' the underlying bytes as long integers, or floating point values, or n-dimensional lists of integers. This makes for very efficient binary file format interpretations, without having to create more copies of the bytes.


Here is python3 code.

#!/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))

Let me make plain where lies the glitch in understanding here.

The questioner, like myself, expected to be able to create a memoryview that selects a slice of an existing array (for example a bytes or bytearray). We therefore expected something like:

desired_slice_view = memoryview(existing_array, start_index, end_index)

Alas, there is no such constructor, and the docs don't make a point of what to do instead.

The key is that you have to first make a memoryview that covers the entire existing array. From that memoryview you can create a second memoryview that covers a slice of the existing array, like this:

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

In short, the purpose of the first line is simply to provide an object whose slice implementation (dunder-getitem) returns a memoryview.

That might seem untidy, but one can rationalize it a couple of ways:

  1. Our desired output is a memoryview that is a slice of something. Normally we get a sliced object from an object of that same type, by using the slice operator [10:20] on it. So there's some reason to expect that we need to get our desired_slice_view from a memoryview, and that therefore the first step is to get a memoryview of the whole underlying array.

  2. The naive expection of a memoryview constructor with start and end arguments fails to consider that the slice specification really needs all the expressivity of the usual slice operator (including things like [3::2] or [:-4] etc). There is no way to just use the existing (and understood) operator in that one-liner constructor. You can't attach it to the existing_array argument, as that will make a slice of that array, instead of telling the memoryview constructor some slice parameters. And you can't use the operator itself as an argument, because it's an operator and not a value or object.

Conceivably, a memoryview constructor could take a slice object:

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

... but that's not very satisfactory, since users would have to learn about the slice object and what its constructor's parameters mean, when they already think in terms of the slice operator's notation.


Excellent example by Antimony. Actually, in Python3, you can replace data = 'x'*n by data = bytes(n) and put parenthesis to print statements as below:

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)

참고URL : https://stackoverflow.com/questions/18655648/what-exactly-is-the-point-of-memoryview-in-python

반응형