programing

파이썬에서 메모리 뷰의 포인트는 정확히 무엇입니까?

linuxpc 2023. 9. 8. 21:13
반응형

파이썬에서 메모리 뷰의 포인트는 정확히 무엇입니까?

메모리 보기에서 설명서 확인:

메모리뷰 객체는 Python 코드가 버퍼 프로토콜을 지원하는 객체의 내부 데이터에 복사 없이 접근할 수 있게 해줍니다.

클래스 메모리 보기(obj)

참조 obj.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
>>> 

위에서 수집한 내용은 다음과 같습니다.

메모리 뷰 객체를 생성하여 버퍼 객체의 내부 데이터를 복사하지 않고 노출시키지만, (객체가 제공하는 메소드를 호출하여) 객체에 유용한 작업을 수행하기 위해서는 복사본을 생성해야 합니다!

일반적으로 큰 객체가 있을 때는 메모리 뷰(또는 이전 버퍼 객체)가 필요하며, 슬라이스도 클 수 있습니다.큰 슬라이스를 만들거나 작은 슬라이스를 많이 만들 때는 효율성이 향상되어야 합니다.

위의 계획으로, 누군가가 제가 여기서 놓치고 있는 것을 설명해 주지 않는 한, 저는 그것이 어떤 상황에서도 어떻게 유용할 수 있을지 모르겠습니다.

편집1:

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

사람들은 해결책을 제안합니다. 예를 들어 많은 문자열 함수와 정규 함수는 포인터를 발전시키는 데 사용할 수 있는 위치 인수를 사용합니다.두 . 둘째는 에두지가다한다째결다다결,,e째에nsr한e두hte지:ss가lt'sd,teodkeuro 단점을 극복하기 위해 코딩 스타일을 변경해야 하는 것이고, 둘째, 모든 함수에 regex 함수와 같은 위치 인수가 있는 것은 아닙니다.startswith하다, 하다,encode()/decode()하지 마.

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

편집2:

코드 샘플을 사용하면 상황이 더 명확해질 수 있습니다.이것이 제가 하고 싶은 것이고, 제가 기억을 보는 것이 언뜻 볼 때 제가 할 수 있을 것이라고 생각한 것입니다.내가 찾고 있는 기능에 대해 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 = b'x'*n
    start = time.time()
    b = data
    while b:
        b = b[1:]
    print(f'     bytes {n} {time.time() - start:0.3f}')

for n in (100000, 200000, 300000, 400000):
    data = b'x'*n
    start = time.time()
    b = memoryview(data)
    while b:
        b = b[1:]
    print(f'memoryview {n} {time.time() - start:0.3f}')

내 컴퓨터에서, 나는

     bytes 100000 0.211
     bytes 200000 0.826
     bytes 300000 1.953
     bytes 400000 3.514
memoryview 100000 0.021
memoryview 200000 0.052
memoryview 300000 0.043
memoryview 400000 0.077

반복되는 문자열 슬라이싱의 2차 복잡성을 명확하게 알 수 있습니다.400,000회만 반복해도 이미 관리가 불가능합니다..memoryviewfast.version은형을다럼며다럼n며형을gt .

편집: 이 작업은 CPython에서 수행되었습니다.Pypy에는 4.0.1까지 메모리 뷰가 2차 성능을 갖도록 하는 버그가 있었습니다.

memoryview개체는 인덱싱만 지원하면 되는 이진 데이터의 하위 집합이 필요할 때 유용합니다.다른 API에 전달하기 위해 슬라이스를 생성(그리고 잠재적으로 큰 새 객체를 생성)할 필요가 없는 대신 다음을 수행할 수 있습니다.memoryview물건.

의 한 예는 API 는 과 의 과 는 의struct모듈. 조각통과하는 대신 대신 of slice 통과하는 the a in of passing instead 모듈 조각 large of <nat> <nat>bytes값을 분석하기 C을한체은를합니다 a기를natao기을한s,체ucd memoryview값을 추출하는 데 필요한 지역만 선택할 수 있습니다.

memoryview체,실을 지원합니다.struct을 푸는 것; 을는것로할수다의다수할faenkt로g은e을의uy;로n;을bytes는체용,용체t는가h.cast()기본 바이트를 긴 정수, 부동 소수점 값 또는 n차원 정수 목록으로 '선택'합니다.이렇게 하면 바이트의 복사본을 더 만들 필요 없이 매우 효율적인 이진 파일 형식 해석이 가능합니다.

여기 이해의 결함이 어디에 있는지 분명히 말씀드리겠습니다.

질문자는 저와 마찬가지로 기존 배열의 조각(예: 바이트 또는 배열별)을 선택하는 메모리 뷰를 생성할 수 있을 것으로 기대했습니다.따라서 우리는 다음과 같은 것을 기대했습니다.

desired_slice_view = memoryview(existing_array, start_index, end_index)

아, 그런 시공자는 없고, 문서들은 그 대신 무엇을 해야 하는지 중요하게 생각하지 않습니다.

핵심은 먼저 기존 배열 전체를 포괄하는 메모리 뷰를 만들어야 한다는 것입니다.이 메모리 보기에서 다음과 같이 기존 배열의 조각을 포함하는 두 번째 메모리 보기를 만들 수 있습니다.

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

간단히 말해서, 첫 번째 줄의 목적은 단순히 슬라이스 구현(dunder-getitem)이 메모리 뷰를 반환하는 객체를 제공하는 것입니다.

어수선해 보이지만 몇 가지 방법으로 합리화할 수 있습니다.

  1. 우리가 원하는 출력은 무언가의 한 조각인 메모리 뷰입니다.일반적으로 슬라이스 연산자 [10:20]를 사용하여 동일한 유형의 개체에서 슬라이스 개체를 가져옵니다.따라서 메모리 뷰에서 원하는_slice_view를 얻을 필요가 있으며, 따라서 첫 번째 단계는 전체 기본 어레이의 메모리 뷰를 얻는 것입니다.

  2. 시작 인수와 종료 인수를 가진 메모리 뷰 생성자의 순진한 기대는 슬라이스 사양에 일반적인 슬라이스 연산자의 표현력([3::2] 또는 [:-4] 등)이 실제로 필요하다는 것을 고려하지 못합니다.기존의 (그리고 이해되는) 연산자를 원-라이너 컨스트럭터에서 그냥 사용할 수 있는 방법은 없습니다.메모리 뷰 생성기에 일부 슬라이스 매개 변수를 알려주는 대신 해당 배열의 슬라이스를 만들기 때문에 기존_array 인수에 연결할 수 없습니다.그리고 연산자 자체를 논쟁으로 사용할 수는 없습니다. 왜냐하면 그것은 값이나 대상이 아니라 연산자이기 때문입니다.

메모리 뷰 생성자는 다음과 같은 슬라이스 객체를 취할 수 있습니다.

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

... 그러나 사용자가 이미 슬라이스 연산자의 표기법 측면에서 생각할 때 슬라이스 객체와 생성자의 매개 변수가 무엇을 의미하는지에 대해 알아야 하기 때문에 이는 매우 만족스럽지 않습니다.

여기 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))

안티몬의 훌륭한 예입니다.실제로 Python3에서는 데이터 = 'x'*n을 데이터 = 바이트(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)

다음 코드가 더 잘 설명할 수 있습니다.당신이 어떻게 그들을 통제할 수 있는지에 대한 통제력이 없다고 가정해보세요.foreign_func구현됩니다.당신은 그것을 전화할 수 있습니다.bytes직접 또는 A와 함께memoryview다음 바이트 중에서:

from pandas import DataFrame
from timeit import timeit


def foreign_func(data):
    def _foreign_func(data):
        # Did you know that memview slice can be compared to bytes directly?
        assert data[:3] == b'xxx'
    _foreign_func(data[3:-3])


# timeit
bytes_times = []
memoryview_times = []
data_lens = []
for n in range(1, 10):
    data = b'x' * 10 ** n
    data_lens.append(len(data))
    bytes_times.append(timeit(
        'foreign_func(data)', globals=globals(), number=10))
    memoryview_times.append(timeit(
        'foreign_func(memoryview(data))', globals=globals(), number=10))


# output
df = DataFrame({
    'data_len': data_lens,
    'memoryview_time': memoryview_times,
    'bytes_time': bytes_times
})
df['times_faster'] = df['bytes_time'] / df['memoryview_time']
print(df)
df[['memoryview_time', 'bytes_time']].plot()

결과:

     data_len  memoryview_time  bytes_time   times_faster
0          10         0.000019    0.000012       0.672033
1         100         0.000016    0.000011       0.690320
2        1000         0.000016    0.000013       0.833314
3       10000         0.000016    0.000037       2.387100
4      100000         0.000016    0.000086       5.300594
5     1000000         0.000018    0.001134      63.357466
6    10000000         0.000009    0.028672    3221.528855
7   100000000         0.000009    0.258822   28758.547214
8  1000000000         0.000009    2.779704  292601.789177

calling with bytes gets exponentially slower

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

반응형