27P by GN⁺ 3일전 | ★ favorite | 댓글 1개
  • 파이썬의 연산·메모리·입출력 성능 수치를 체계적으로 측정한 벤치마크 결과로, 각 연산의 시간과 메모리 사용량을 정량화함
  • 속도 측면에서는 속성 접근 14ns, 리스트 추가 29ns, 파일 열기 9μs, FastAPI 응답 8.6μs 등 다양한 연산의 상대적 지연시간을 제시
  • 메모리 측면에서는 빈 문자열 41바이트, 정수 28바이트, 빈 리스트 56바이트, 빈 딕셔너리 64바이트, 빈 프로세스 16MB 등 구체적 수치를 제시
  • 데이터 구조·직렬화·비동기 처리 등 각 영역별로 표준 라이브러리와 대안 라이브러리(orjson, msgspec 등)의 성능 차이를 비교
  • 핵심 교훈으로는 파이썬 객체의 높은 메모리 오버헤드, dict/set의 빠른 조회, __slots__의 메모리 절감 효과, 비동기 처리의 오버헤드 인식이 강조됨

개요

  • 파이썬 개발자가 알아야 할 성능 지표를 정리한 자료로, 연산 속도와 메모리 사용량을 실제 측정값으로 제시
  • 벤치마크는 CPython 3.14.2, Mac Mini M4 Pro (ARM, 14코어, 24GB RAM) 환경에서 수행
  • 결과는 상대적 비교에 중점을 두며, 코드와 데이터는 GitHub 저장소에서 공개됨

메모리 사용량 (Memory Costs)

  • 빈 파이썬 프로세스는 15.73MB 메모리 사용
  • 문자열은 기본 41바이트, 문자 1개당 1바이트 추가
    • 예: 빈 문자열 41B, 100자 문자열 141B
  • 숫자형은 작은 정수(0–256) 28B, 큰 정수(1000)도 28B, 매우 큰 정수(10ⁱ⁰⁰) 72B, 부동소수점 24B
  • 컬렉션의 기본 크기: 리스트 56B, 딕셔너리 64B, 세트 216B
    • 1,000개 항목일 때 리스트 35.2KB, 딕셔너리 63.4KB, 세트 59.6KB
  • 클래스 인스턴스: 일반 클래스(5속성) 694B, __slots__ 클래스 212B
    • 1,000개 인스턴스 시 일반 클래스 165.2KB, __slots__ 클래스 79.1KB

기본 연산 (Basic Operations)

  • 산술 연산: 정수 덧셈 19ns, 실수 덧셈 18.4ns, 정수 곱셈 19.4ns
  • 문자열 연산: 연결 39.1ns, f-string 64.9ns, .format() 103ns, % 포맷 89.8ns
  • 리스트 연산: append() 28.7ns, 리스트 컴프리헨션(1,000개) 9.45μs, 동일 for문 11.9μs
    • 리스트 컴프리헨션이 for문보다 약 26% 빠름

컬렉션 접근 및 반복 (Collection Access and Iteration)

  • 키/인덱스 접근: 딕셔너리 조회 21.9ns, 세트 멤버십 19ns, 리스트 인덱스 접근 17.6ns
    • 리스트 멤버십(1,000개)은 3.85μs로, 세트/딕셔너리보다 약 200배 느림
  • 길이 확인: len()은 리스트 18.8ns, 딕셔너리 17.6ns, 세트 18ns
  • 반복: 리스트(1,000개) 7.87μs, 딕셔너리 8.74μs, sum() 1.87μs

클래스와 속성 (Class and Object Attributes)

  • 속성 접근 속도: 일반 클래스와 __slots__ 클래스 모두 읽기 14.1ns, 쓰기 약 16ns
  • 기타 연산: @property 읽기 19ns, getattr() 13.8ns, hasattr() 23.8ns
  • __slots__ 사용 시 메모리 절감 효과는 2배 이상, 접근 속도는 동일 수준

JSON 및 직렬화 (JSON and Serialization)

  • 표준 라이브러리 대비 대안 라이브러리 성능
    • orjson은 복잡 객체 직렬화 시 310ns로, json의 2.65μs보다 8배 이상 빠름
    • msgspec은 445ns, ujson은 1.64μs
  • 역직렬화에서도 orjson이 839ns로 가장 빠름
  • Pydantic: model_dump_json() 1.54μs, model_validate_json() 2.99μs

웹 프레임워크 (Web Frameworks)

  • 동일 JSON 응답 기준 FastAPI 8.63μs, Starlette 8.01μs, Litestar 8.19μs, Flask 16.5μs, Django 18.1μs
  • FastAPI는 Django보다 약 2배 빠른 응답 속도

파일 입출력 (File I/O)

  • 파일 열기·닫기 9.05μs, 1KB 읽기 10μs, 1MB 읽기 33.6μs
  • 쓰기: 1KB 35.1μs, 1MB 207μs
  • Picklejson보다 직렬화·역직렬화 모두 약 2배 빠름 (pickle.dumps() 1.3μs, json.dumps() 2.72μs)

데이터베이스 및 캐시 (Database and Persistence)

  • SQLite: insert 192μs, select 3.57μs, update 5.22μs
  • diskcache: set 23.9μs, get 4.25μs
  • MongoDB: insert 119μs, find_one 121μs
  • SQLite가 읽기 속도에서 가장 빠르며, diskcache는 쓰기 성능이 우수

함수 호출 및 예외 (Function and Call Overhead)

  • 함수 호출: 빈 함수 22.4ns, 메서드 23.3ns, lambda 19.7ns
  • 예외 처리: try/except(정상) 21.5ns, 예외 발생 시 139ns
  • 형 검사: isinstance() 18.3ns, type() 비교 21.8ns

비동기 오버헤드 (Async Overhead)

  • 코루틴 생성 47ns, run_until_complete 27.6μs
  • asyncio.sleep(0) 39.4μs, gather(10 coroutines) 55μs
  • 동기 함수 호출(20ns) 대비 비동기 실행(28μs) 은 약 1,000배 느림

주요 교훈 (Key Takeaways)

  • 파이썬 객체의 메모리 오버헤드는 크며, 빈 리스트조차 56바이트 사용
  • 딕셔너리·세트 조회는 리스트 탐색보다 수백 배 빠름
  • orjson, msgspec 등 대체 JSON 라이브러리는 표준보다 3~8배 빠름
  • 비동기 처리는 오버헤드가 크므로 병렬성이 필요한 경우에만 사용 권장
  • __slots__ 는 메모리를 절반 이하로 줄이면서 성능 손실이 거의 없음
Hacker News 의견들
  • 많은 사람들이 “Python에서 지연(latency) 수치를 신경 써야 한다면 다른 언어를 써야 한다”고 하지만, 나는 동의하지 않음
    Instagram, Dropbox, OpenAI 같은 대규모 코드베이스도 Python으로 성장했음. 결국 성능 문제를 만나게 되는데, 다른 언어로 옮기지 않고 Python 안에서 해결할 수 있는 능력이 중요함
    대부분의 성능 문제는 언어의 한계가 아니라 비효율적인 코드 때문임. 예를 들어, 불필요하게 함수 호출을 1만 번 반복하는 루프 같은 경우임
    내가 만든 Python latency quiz도 참고할 만함

    • 나는 Python으로 작성된 시스템의 성능 최적화를 담당하고 있음. 하지만 이런 숫자들은 실제로 문제가 되기 전까지는 의미가 없음. 문제가 생기면 직접 측정함. 메서드 호출을 아끼려는 식으로 코드를 짜면 Python의 장점을 잃게 됨
    • Python은 기본적인 연산조차 느림. 함수 호출이나 딕셔너리 접근 같은 단순한 작업에서도 느림. 사실 Python이 살아남은 건 C/C++ 기반 라이브러리들(Numpy 등) 덕분임
    • 이런 수치는 Python만의 문제가 아님. Zig에서도 CPU 사이클이나 캐시 미스를 고려함. 모든 언어에는 특정 연산의 지연이 존재함. Python을 안 쓸 이유는 있겠지만, 이건 그 이유가 아님
    • 일부 연산은 대체 모듈을 쓰면 개선됨. 이런 지식을 아는 건 중요하지만, 정말 필요한 사람이라면 이미 알고 있을 것임. 그래도 Python은 프로토타이핑에 훌륭한 언어임
    • 우리 빌드 시스템도 Python으로 되어 있어서, 성능을 개선하면서도 Python을 유지하고 싶음. 그래서 이런 수치가 매우 중요함
  • 역설적으로, 이런 수치가 중요해지는 순간 Python은 그 일에 적합한 도구가 아님

    • Python 코드를 유지하면서 핵심 성능 부분만 C나 Rust 확장으로 내리는 게 현실적인 접근임. numpy, pandas, PyTorch가 그렇게 함.
      실제로는 코드를 계측하고(pyspy 같은 도구로) 병목을 찾는 게 중요함. 리스트에 원소 추가 속도를 걱정하는 수준이라면, 그 연산은 Python에서 하면 안 됨
    • 나는 20년 동안 Python으로 일했지만 이런 숫자를 알아야 할 필요는 없었음. 대신 프로파일링과 Cython, SWIG, JIT 같은 도구로 해결함
    • 이런 수치가 중요할 정도의 애플리케이션이라면 Python은 너무 고수준 언어라 최적화가 어렵다고 생각함
    • 하지만 나는 Python으로 대규모 데이터 파이프라인을 구축했음. turbodbc + pandas 조합으로 C++ 수준의 속도를 냄. 메모리는 더 쓰지만 인건비를 생각하면 훨씬 효율적임.
      Python과 C의 상호운용성 덕분에 이런 접근이 가능함. Zig도 점점 좋아지고 있음. Python으로 비행기를 조종하진 않겠지만, 리소스 감각은 여전히 중요함
    • 이런 수치는 최후의 수단임. 디스크 I/O, 네트워크, 알고리즘 복잡도 등 일반적인 병목을 다 해결한 뒤에야 고려할 문제임
  • 빈 문자열이 몇 바이트를 차지하는지 아는 건 별 의미가 없음. 중요한 건 시간·공간 복잡도를 이해하는 것임.
    int가 28바이트라는 걸 아는 것보다, 프로그램이 성능 요구사항을 충족하는지 판단하고, 그렇지 않다면 더 나은 알고리즘을 찾는 게 중요함

    • 하지만 성능은 항상 누수되는 추상화임. 우리가 의식하든 아니든 코드 전반에 영향을 줌.
      예를 들어 문자열 덧셈이 O(n²)이라는 사실은 Python의 f-string 설계에도 영향을 줌.
      딕셔너리가 빠르기 때문에 Python 전반에서 널리 쓰이는 것도 같은 맥락임.
      이런 수치는 그 암묵적 지식을 수치로 정당화해주는 역할을 함
    • int가 28바이트라는 건, 대량의 객체를 생성해야 하는 문제에서는 실제로 중요함.
      Eric Raymond가 Reposurgeon으로 GCC를 마이그레이션할 때 겪은 문제를 다룬 을 떠올리게 함
  • 제목이 혼란스러운데, 사실 Jeff Dean의 2012년 논문 “Latency Numbers Every Programmer Should Know” 를 패러디한 것임.
    이런 식의 제목 장난은 CS 논문에서 흔함

    • “latency numbers considered harmful is all you need” 같은 제목으로 논문을 쓰면 학계에서 인기 폭발일 듯함
    • 하지만 이 글의 저자는 진지하게 쓴 것 같음. 독자들이 제목을 오해한 건 아님
    • 제목이 성립하려면 숫자들이 실제로 유용해야 하는데, 너무 많고 실질적이지 않음
    • 참고로 Jeff Dean의 원문은 2012년보다 훨씬 이전에 작성된 것으로 보임.
      Google 초창기 검색엔진의 RAM vs Disk 설계를 위한 내부 자료였음.
      이후 플래시 메모리 등장으로 수치가 바뀌었고, Jeff가 유전체 데이터를 플래시에서 직접 서비스하려고 압축 알고리즘을 만들었다는 일화도 있음
  • 대부분의 Python 개발자는 이런 저수준 성능 세부사항보다 더 중요한 일에 집중해야 함.
    이런 자료는 참고용으로는 좋지만 실제로는 드물게만 필요함

    • 하지만 사용하는 도구에 대한 일반 지식은 언제나 가치 있음. 지적 자산이자 특정 상황에서는 큰 도움이 됨
    • 한계에 부딪히면 C로 구현된 모듈을 찾거나 직접 작성하면 됨. Python은 원래 그렇게 발전해왔음
    • 나도 대부분의 경우 “충분히 빠르다”는 감각으로 일했음. 이번 자료는 그 감각을 숫자로 확인하게 해줌
  • 문자열 크기 설명이 잘못됨. Python에는 문자당 1, 2, 4바이트를 쓰는 세 가지 문자열 타입이 있음
    자세한 내용은 이 블로그 참고

  • 글의 제목과 예시가 다소 부정확함.
    예를 들어 “item in set이 item in list보다 200배 빠르다”는 건 멤버십 테스트 얘기지, 반복(iteration) 속도 비교가 아님.
    그래도 전체적으로 형식과 구성은 매력적

  • 클래스 인스턴스 생성 시간 측정이 빠져 있음.
    나는 코드 리팩터링 후, 단순 리스트 구조를 클래스로 바꿨더니 실행 시간이 수 마이크로초 → 수 초로 늘어남.
    이런 경우를 측정해줬으면 좋겠음

    • 의사에게 “이거 하면 아파요” 했더니 “그럼 하지 마세요”라고 한 농담이 떠오름.
      클래스 남용이 문제일 수도 있음. 단순한 리스트 구조가 더 나을 때도 있음
    • 클래스 인스턴스 생성 자체는 보통 성능 문제가 아님.
      오히려 객체지향을 잘못 사용했을 가능성이 큼.
      코드를 StackOverflow나 CodeReview.SE에 올려서 피드백을 받는 게 좋음
  • 나는 이 글을 “현대 Python이 뭔가 근본적으로 잘못된 게 있나”라는 관점에서 흥미롭게 읽었음.
    하지만 이런 숫자를 “모두 알아야 한다”는 주장에는 동의하지 않음.
    핵심 연산 몇 가지 정도만 감각적으로 알고 있으면 충분함

  • Python의 small int 캐싱 범위는 0~256이 아니라 -5~256임.
    이 때문에 동일성(is)동등성(==) 을 혼동하는 초보자들이 자주 헷갈림

    • Java도 비슷한 동작을 함. 초보자에게는 혼란스러울 수 있음