본문 바로가기
카테고리 없음

memoryview

by redcubes 2025. 1. 13.

memoryview는 파이썬의 버퍼 프로토콜을 통해 배열이나 바이트열 등의 내부 메모리에 직접 접근하는 기능을 제공하여, 대용량 데이터를 다룰 때 복사를 최소화해 성능을 향상시킬 수 있는 것이 핵심이다. 이를 입출력에 활용하면 빠른 IO를 구현하는 데 도움을 받을 수 있다.

다만 다음과 같은 점들을 유의하는 것이 좋다.

  1. 버퍼 프로토콜을 지원하는 객체에 한정
    memoryview는 버퍼 프로토콜을 지원하는 객체(예: bytes, bytearray, array.array, numpy.ndarray 등)에 대해서만 사용할 수 있다. 전통적인 list나 tuple에는 사용할 수 없다.
  2. 제로 카피(Zero Copy)와 빠른 슬라이싱
    memoryview를 사용하면 데이터를 슬라이싱할 때 추가 복사가 발생하지 않는다. 큰 바이트열을 자주 슬라이싱하거나 부분을 수정해야 할 때 유용하다.
  3. 파일 입출력, 소켓 통신 시 활용
    표준 라이브러리나 일부 함수(예: socket.sendall())는 버퍼 프로토콜을 지원하므로, memoryview를 직접 넘겨 빠르게 입출력을 수행할 수 있다. 예를 들어 파일에 쓸 때도 write(memoryview_obj)와 같이 사용 가능하다.

예시 코드는 다음과 같다.

import os

data = bytearray(b"Hello World!")

# memoryview 생성
mv = memoryview(data)

# 일부 구간에 바로 접근해서 수정 (복사 없이)
mv[6:] = b"Python!"

# 파일에 쓰기 (간단 예시)
with open("example.bin", "wb") as f:
    f.write(mv)  # memoryview를 직접 write 가능

# 소켓에서 send 메서드 사용 예시
import socket

server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(("localhost", 9999))
server_socket.listen(1)
print("서버 대기 중...")

client_socket, addr = server_socket.accept()
print("클라이언트 연결:", addr)

# 클라이언트에게 데이터 전송
client_socket.sendall(mv)  # memoryview를 직접 전송
client_socket.close()
server_socket.close()

위 예시처럼 memoryview를 활용해 파일이나 소켓에 데이터를 직접 쓰는 것이 가능하다. 이때 내부 버퍼를 건드리는 것이므로, 필요 이상의 복사를 줄여 빠른 처리가 가능하다. 다만, 이로 인한 실제 성능 향상 폭은 사용 환경(파이썬 버전, OS, IO 방식 등)에 따라 달라질 수 있다.

결론적으로, memoryview는 큰 데이터를 다룰 때 불필요한 복사를 줄여 빠른 입출력을 구현하는 데 쓸 수 있는 유용한 도구이다. 다만, 항상 “더 빠르다”는 보장은 없으므로, 구체적인 용례와 환경에서 성능을 테스트해보는 것이 중요하다.

 

파이썬의 표준입출력(특히 대용량 데이터를 다뤄야 할 때)을 빠르게 처리하기 위해서는, 텍스트 모드 대신 이진 모드(바이너리 모드)로 접근해 최소한의 복사만 이루어지도록 하는 것이 중요하다. memoryview는 이러한 버퍼에 직접 접근해 복사를 줄이는 기능을 제공한다. sys.stdin이나 sys.stdout은 텍스트 모드로 열려 있으므로, 이진 모드 접근을 위해서는 sys.stdin.buffer, sys.stdout.buffer와 같은 속성을 사용해야 한다.

기본 아이디어

  • sys.stdin.buffer.read()나 sys.stdin.buffer.readline() 등으로 표준입력을 이진 데이터로 읽는다.
  • 그 결과를 memoryview로 감싸 필요한 부분만 슬라이싱하거나 수정한다.
  • 처리 후 sys.stdout.buffer.write()를 이용해 출력한다.
    (출력 후 필요하다면 flush()로 버퍼를 비워준다.)

간단 예시

import sys

# 표준입력에서 모든 데이터를 이진 모드로 읽어옴
binary_data = sys.stdin.buffer.read()

# memoryview로 감싸기
mv = memoryview(binary_data)

# (예) 앞부분 10바이트만 따로 처리해야 하는 경우
header = mv[:10]
body = mv[10:]

# 처리 후 표준출력에 내보내기
sys.stdout.buffer.write(header)
sys.stdout.buffer.write(body)
sys.stdout.buffer.flush()

위 예시는 매우 단순화된 형태이며, 실제로는 다음과 같은 상황에서 확장해 활용할 수 있다.

  1. 대용량 파일/데이터 파이프
    표준입출력을 통해 큰 바이너리 파일을 스트리밍할 때, memoryview를 활용해 불필요한 복사를 줄이고 곧바로 다른 곳(파일, 네트워크 소켓 등)에 전송할 수 있다.
  2. 부분 업데이트/부분 전송
    전체 바이트열 중 특정 부분만 수정하거나 압축 알고리즘의 헤더부만 따로 떼어내야 할 때, memoryview를 통해 슬라이싱으로 바로 접근해 처리할 수 있다.
  3. 실시간 스트리밍
    초당 큰 데이터가 들어오는 상황에서, 일정 크기(chunk_size)로 나눠 반복문을 돌면서 memoryview로 필요한 부분만 전처리 후 곧바로 출력 버퍼에 쓰는 식으로 사용할 수 있다.

Chunk 단위 처리 예시

import sys

chunk_size = 1024  # 예시로 1KB 단위로 읽기
while True:
    chunk = sys.stdin.buffer.read(chunk_size)
    if not chunk:
        break
    mv = memoryview(chunk)
    # (예) mv의 일부만 필요하다면 mv[0:512] 등 슬라이싱 가능
    sys.stdout.buffer.write(mv)
    sys.stdout.buffer.flush()

이처럼 memoryview는 표준입출력의 바이너리 버퍼에 대해 불필요한 복사를 최소화하여 빠른 IO 처리를 가능하게 해준다. 물론 실제 성능 향상 폭은 OS나 파이썬 버전, 버퍼 크기 등에 따라 달라지므로, 상황에 맞춰 테스트해 보는 것이 좋다.


os.read와 조합하기

os.read와 memoryview를 활용하여 표준입출력 코딩테스트의 성능을 극대화하는 방법은, 데이터를 빠르게 읽고 처리하기 위해 저수준 입출력 방식과 고성능 데이터 접근 방식을 결합하는 것이다. 이를 하나씩 살펴보면 다음과 같다.


1. os.read의 활용

os.read는 파이썬의 표준 sys.stdin.read보다 저수준 파일 디스크립터 수준에서 데이터를 직접 읽어들인다. 이를 사용하면 불필요한 버퍼링 및 파이썬 인터프리터의 추가 처리 과정을 줄여 속도를 크게 향상시킬 수 있다.

  • 장점:
    • 표준 입력 전체를 한 번에 읽어 들임으로써 다중 호출로 인한 오버헤드를 줄임.
    • 입력 크기를 미리 계산(os.fstat(0).st_size)해 효율적으로 읽기 가능.
  • 구조:
    import os
    
    data = os.read(0, os.fstat(0).st_size)  # 표준 입력을 바이너리 형태로 읽음
    

2. memoryview의 활용

memoryview는 메모리 상의 데이터 버퍼를 복사 없이 직접 접근할 수 있는 고성능 도구로, 대규모 데이터를 처리하는 경우 성능 향상에 크게 기여한다.

  • 장점:
    • 데이터를 복사하지 않고 뷰(view)만 생성하여 메모리 효율적.
    • 슬라이싱이나 바이트 단위로 데이터를 처리할 때 빠름.
  • 구조:
    view = memoryview(data)  # os.read로 읽은 데이터를 memoryview로 래핑
    

3. 결합한 성능 극대화

os.read로 데이터를 읽고, memoryview를 활용해 데이터를 처리하면 다음과 같은 최적화가 가능하다:

  1. 전체 입력 읽기: 표준 입력을 모두 한 번에 읽고 처리할 수 있어 속도가 빠르다.
  2. 바이너리 데이터 처리: 데이터가 바이너리로 들어오므로, 필요한 부분만 슬라이싱하여 파싱 가능.
  3. 복사 없이 처리: 메모리 복사를 최소화하고 데이터를 필요한 범위에서만 작업.

4. 예제 코드

아래는 os.read와 memoryview를 결합하여 빠르게 데이터를 처리하는 예제다.

import os

# 1. 표준 입력 전체 읽기
data = os.read(0, os.fstat(0).st_size)

# 2. 메모리뷰 생성
view = memoryview(data)

# 3. 입력 데이터를 한 줄씩 처리
start = 0
for i in range(len(view)):
    if view[i] == 10:  # '\n' (LF) 문자를 발견했을 때
        line = view[start:i].tobytes().decode('utf-8')  # 한 줄 추출
        # 처리할 로직 (예: 파싱)
        print(line)  # 출력 (테스트용)
        start = i + 1

5. 성능 최적화 이유

  1. 한 번의 입력 처리: 입력을 한 번에 읽고, 파싱 중에 추가적인 I/O 연산을 하지 않음.
  2. 메모리 사용 최소화: memoryview로 메모리 복사 없이 데이터를 슬라이싱하여 효율적.
  3. 저수준 API 활용: 파이썬 인터프리터의 고수준 입력 처리보다 빠름.

6. 활용 시 주의사항

  • 입력 크기 제한: 입력 크기가 매우 큰 경우 메모리 부족 문제가 발생할 수 있으니 주의해야 한다.
  • 데이터 파싱: 바이너리 데이터를 처리하므로, 필요한 경우 적절히 디코딩해야 한다.
  • 환경 의존성: os.read와 같은 저수준 API는 시스템 의존적이므로, 특정 플랫폼에서만 동작할 수 있다.

이 방법은 주로 입력 크기가 크고, 속도가 중요한 코딩테스트에서 유용하다. 입력 처리 단계의 병목을 최소화하여 연산 로직에 더 많은 시간을 사용할 수 있도록 돕는다.

 

https://python101.tistory.com/entry/%ED%8C%8C%EC%9D%B4%EC%8D%ACPython-memoryview-%EC%82%AC%EC%9A%A9%EB%B2%95-%EC%A0%95%EB%A6%AC#google_vignette

 

파이썬(Python) memoryview 사용법 정리

memoryview는 Python에서 제공하는 내장 라이브러리로, 다양한 데이터 유형의 메모리 버퍼에 대한 안전한 접근 방법을 제공합니다. 이를 통해 C 언어와 같은 저수준의 메모리 조작을 수행할 수 있습

python101.tistory.com

https://docs.python.org/ko/3/c-api/memoryview.html

 

MemoryView objects

A memoryview object exposes the C level buffer interface as a Python object which can then be passed around like any other object.

docs.python.org

https://park-dev-diary.tistory.com/15

 

memoryview 성능 테스트 ( feat.timeit, 버퍼프로토콜 )

timeit 라이브러리는 작은단위의 코드에 실행시간을 테스트할 수 있는 유용한 도구입니다. 이 도구를 사용하여 파이썬에서 제공해주는 단순한 바이트배열과 memoryview 라는 객체를 활용하였을 때

park-dev-diary.tistory.com

https://python.flowdas.com/library/stdtypes.html#binary-sequence-types-bytes-bytearray-memoryview

 

내장형 — 파이썬 설명서 주석판

다음 섹션에서는 인터프리터에 내장된 표준형에 관해 설명합니다. 기본 내장 유형은 숫자, 시퀀스, 매핑, 클래스, 인스턴스 및 예외입니다. 일부 컬렉션 클래스는 가변입니다. 제자리에서 멤버

python.flowdas.com