Python 스크립트가 동등한 C ++ 프로그램만큼 빠르지 못하게하는 기술적 제한이나 언어 기능이 있습니까?


10

저는 오랜 파이썬 사용자입니다. 몇 년 전, 저는 C ++이 속도 측면에서 무엇을 제공 할 수 있는지 배우기 시작했습니다. 이 기간 동안 저는 프로토 타입 도구로 Python을 계속 사용합니다. 이것은 좋은 시스템 인 것 같습니다. 파이썬으로 민첩한 개발, C ++에서 빠른 실행.

최근에 저는 파이썬을 점점 더 많이 사용하고 있으며, 언어와 함께 초기에 빨리 사용했던 모든 함정과 반 패턴을 피하는 방법을 배우고 있습니다. 특정 기능 (목록 이해, 열거 등)을 사용하면 성능이 향상 될 수 있음을 이해합니다.

그러나 파이썬 스크립트가 동등한 C ++ 프로그램만큼 빠르지 못하게하는 기술적 제한이나 언어 기능이 있습니까?


2
예, 그럴 수 있습니다. Python 컴파일러의 최신 기술에 대해서는 PyPy 를 참조하십시오 .
Greg Hewgill

5
파이썬의 모든 변수는 다형성이므로 변수 유형은 런타임에만 알려집니다. C와 같은 언어에서 (정수 가정) x + y가 보이면 정수를 더합니다. 파이썬에서는 x와 y에 변수 유형에 대한 스위치가 있고 적절한 추가 기능이 선택된 다음 오버플로 검사가 있고 추가가 있습니다. 파이썬이 정적 타이핑을 배우지 않으면이 오버 헤드는 사라지지 않습니다.
nwp

1
@nwp 아니요, 쉬운 방법은 PyPy를 참조하십시오. 여전히 까다 롭고 개방적인 문제는 다음과 같습니다. JIT 컴파일러의 시작 대기 시간을 극복하는 방법, 복잡한 장기 객체 그래프에 대한 할당을 피하는 방법 및 일반적으로 캐시를 잘 활용하는 방법.

답변:


11

몇 년 전에 전 시간 파이썬 프로그래밍 작업을했을 때이 벽에 닿았습니다. 저는 파이썬을 좋아합니다. 정말로 그렇습니다.하지만 성능 조정을 시작했을 때 약간의 충격이있었습니다.

엄격한 Pythonistas가 나를 교정 할 수 있지만 여기에 내가 찾은 것들이 매우 광범위하게 그려져 있습니다.

  • 파이썬 메모리 사용은 무섭습니다. 파이썬은 모든 것을 dict로 표현합니다. 매우 강력하지만 간단한 데이터 유형조차도 거대합니다. "a"문자가 28 바이트의 메모리를 사용했음을 기억합니다. 파이썬에서 빅 데이터 구조를 사용하는 경우 직접 바이트 배열 구현에 의해 지원되므로 numpy 또는 scipy에 의존해야합니다.

이는 다른 언어에 비해 엄청난 양의 메모리를 사용하는 것 외에도 런타임에 추가 수준의 간접 성이 있음을 의미하므로 성능에 영향을 미칩니다.

  • 파이썬에는 전역 인터프리터 잠금이 있습니다. 이는 대부분 프로세스가 단일 스레드로 실행되고 있음을 의미합니다. 여러 프로세스에 작업을 배포하는 라이브러리가있을 수 있지만 32 개 정도의 Python 스크립트 인스턴스를 회전시키고 각 단일 스레드를 실행했습니다.

다른 사람들은 실행 모델과 대화 할 수 있지만, 파이썬은 런타임에 컴파일 한 다음 해석되므로 기계 코드로가는 것은 아닙니다. 또한 성능에 영향을 미칩니다. C 또는 C ++ 모듈에서 쉽게 링크하거나 찾을 수 있지만, 파이썬을 바로 실행하면 성능이 저하됩니다.

이제 웹 서비스 벤치 마크에서 Python은 Ruby 또는 PHP와 같은 런타임시 컴파일 언어와 비교하여 유리합니다. 그러나 대부분의 컴파일 된 언어 뒤에 있습니다. 중간 언어로 컴파일되고 VM (예 : Java 또는 C #)에서 실행되는 언어조차 훨씬 더 좋습니다.

다음은 내가 참조하는 정말 흥미로운 벤치 마크 테스트 세트입니다.

http://www.techempower.com/benchmarks/

(그렇지만 필자는 여전히 파이썬을 매우 사랑하며, 내가 사용하고있는 언어를 선택할 수있는 기회를 얻는다면 이것이 가장 먼저 선택해야합니다.


2
문자열 "a"는 첫 번째 글 머리 기호에 대한 좋은 예가 아닙니다. Java 문자열도 단일 문자열에 대해 상당한 오버 헤드가 있지만 문자열의 길이가 길어질 때마다 일정한 오버 헤드가 발생합니다 (버전, 빌드 옵션 및 문자열 내용에 따라 1-4 바이트). 적어도 사용자를 사용하지 않는 객체는 사용자 정의 객체에 적합합니다 __slots__. PyPy는 이와 관련하여 훨씬 더 잘해야하지만 판단하기에 충분하지 않습니다.

1
두 번째로 지적하는 문제는 특정 구현에만 관련이 있으며 언어에 국한되지 않습니다. 첫 번째 문제는 설명이 필요합니다. 28 바이트의 "무게"는 문자 자체가 아니라 문자열 클래스로 압축되어 자체 메서드 및 속성과 함께 제공된다는 사실입니다. 파이썬 3.3에서 단일 문자를 바이트 배열 (리터럴 b'a ') "18"로 표현하면 무게는 18 바이트이며 응용 프로그램에 실제로 필요한 경우 메모리에서 문자 저장소를 최적화하는 더 많은 방법이 있다고 확신합니다.
레드

C #은 기본적으로 컴파일 할 수 있습니다 (예 : 곧 출시 될 MS 기술, iOS 용 Xamarin).
Den

13

파이썬 참조 구현은 "CPython"인터프리터입니다. 합리적으로 빠르려고 시도하지만 현재 고급 최적화를 사용하지 않습니다. 그리고 많은 사용 시나리오에서 이것은 좋은 일입니다. 일부 중간 코드로의 컴파일은 런타임 직전에 발생하며 프로그램이 실행될 때마다 코드가 새로 컴파일됩니다. 따라서 최적화에 필요한 시간은 최적화로 얻은 시간과 비교되어야합니다. 순 이득이 없으면 최적화는 가치가 없습니다. 매우 오래 실행되는 프로그램 또는 루프가 매우 엄격한 프로그램의 경우 고급 최적화를 사용하는 것이 유용합니다. 그러나 CPython은 적극적인 최적화를 방해하는 일부 작업에 사용됩니다.

  • sysadmin 작업에 사용되는 단기 실행 스크립트 Ubuntu와 같은 많은 운영 체제는 Python을 기반으로 인프라의 상당 부분을 구축합니다. CPython은 작업 속도가 빠르지 만 시작 시간은 거의 없습니다. bash보다 빠르면 좋습니다.

  • CPython은 참조 구현이므로 명확한 의미론을 가져야합니다. 이렇게하면 "foo 연산자의 구현 최적화"또는 "더 빠른 바이트 코드로 컴파일 목록 이해"와 같은 간단한 최적화가 가능하지만 일반적으로 함수 인라이닝과 같은 정보를 파괴하는 최적화는 배제됩니다.

물론 CPython보다 더 많은 Python 구현이 있습니다.

  • 자이 썬은 JVM 위에 구축되었다. JVM은 제공된 바이트 코드를 해석하거나 JIT 컴파일 할 수 있으며 프로파일 가이드 최적화 기능이 있습니다. 시작 시간이 길고 JIT가 시작될 때까지 시간이 걸립니다.

  • PyPy는 최신 기술인 JITting Python VM입니다. PyPy는 Python의 제한된 하위 집합 인 RPython으로 작성되었습니다. 이 부분 집합은 파이썬에서 표현력을 제거하지만 변수의 유형을 정적으로 유추 할 수 있습니다. 그런 다음 RPython으로 작성된 VM을 C로 변환하여 RPython C와 유사한 성능을 제공합니다. 그러나 RPython은 여전히 ​​C보다 표현력이 뛰어나 새로운 최적화를 더 빨리 개발할 수 있습니다. PyPy는 컴파일러 부트 스트랩의 예입니다. PyPy (RPython 아님)는 대부분 CPython 참조 구현과 호환됩니다.

  • Cython은 (RPython과 같이) 정적 입력을 사용하는 호환되지 않는 Python 방언입니다. 또한 C 코드로 변환하고 CPython 인터프리터를 위해 C 확장을 쉽게 생성 할 수 있습니다.

파이썬 코드를 Cython 또는 RPython으로 기꺼이 번역하려는 경우 C와 비슷한 성능을 얻을 수 있습니다. 그러나“Python의 하위 집합”이 아니라“Pythonic 구문이있는 C”로 이해해서는 안됩니다. PyPy로 전환하면 바닐라 Python 코드는 상당한 속도 향상을 가져 오지만 C 또는 C ++로 작성된 확장 프로그램과 인터페이스 할 수 없습니다.

그러나 바닐라 파이썬이 긴 시작 시간 외에 C와 같은 수준의 성능에 도달하지 못하게하는 속성이나 기능은 무엇입니까?

  • 기고자 및 자금. Java 또는 C #과는 달리이 언어를 동급 최고의 언어로 만드는 데 관심이있는 단일 구동 회사는 없습니다. 이것은 주로 자원 봉사자와 가끔 보조금으로 개발을 제한합니다.

  • 늦은 바인딩과 정적 타이핑 부족. 파이썬은 다음과 같이 쓰레기를 쓸 수 있습니다.

    import random
    
    # foo is a function that returns an empty list
    def foo(): return []
    
    # foo is a function, right?
    # this ought to be equivalent to "bar = foo"
    def bar(): return foo()
    
    # ooh, we can reassign variables to a different type – randomly
    if random.randint(0, 1):
       foo = 42
    
    print bar()
    # why does this blow up (in 50% of cases)?
    # "foo" was a function while "bar" was defined!
    # ah, the joys of late binding

    파이썬에서는 언제든지 변수를 재 할당 할 수 있습니다. 캐싱 또는 인라인을 방지합니다. 모든 액세스는 변수를 거쳐야합니다. 이 간접적 인 지시는 성능을 떨어 뜨립니다. 물론 : 코드가 컴파일하기 전에 각 변수에 결정적인 유형을 부여하고 각 변수가 한 번만 할당되도록 이론적으로는 더 효율적인 실행 모델을 선택할 수 있습니다. 이를 염두에 둔 언어는 식별자를 상수로 표시하고 선택적인 유형 주석 ( "점진적 타이핑")을 허용하는 방법을 제공합니다.

  • 의심스러운 객체 모델. 슬롯을 사용하지 않으면 객체에 어떤 필드가 있는지 파악하기가 어렵습니다 (Python 객체는 기본적으로 해시 필드 테이블). 그리고 우리가 거기에 도착하더라도, 우리는 여전히이 필드들이 어떤 타입인지 알지 못합니다. 이것은 C ++에서와 같이 객체를 단단히 압축 된 구조체로 표현하는 것을 방지합니다. (물론 C ++의 객체 표현은 이상적이지 않습니다. 구조체와 같은 특성으로 인해 개인 필드조차도 객체의 공용 인터페이스에 속합니다.)

  • 가비지 콜렉션. 많은 경우 GC를 완전히 피할 수 있습니다. C ++을 사용하면 현재 범위가 남아있을 때 자동으로 파괴되는 객체를 정적으로 할당 할 수 있습니다 Type instance(args);. 그때까지 객체는 살아 있으며 다른 기능에 빌려 줄 수 있습니다. 이는 일반적으로 "통과 기준"을 통해 수행됩니다. Rust와 같은 언어를 사용하면 컴파일러는 그러한 객체에 대한 포인터가 객체의 수명을 초과하지 않는지 정적으로 확인할 수 있습니다. 이 메모리 관리 체계는 완전히 예측 가능하고 매우 효율적이며 복잡한 객체 그래프가없는 대부분의 경우에 적합합니다. 불행히도 파이썬은 메모리 관리를 염두에두고 설계되지 않았습니다. 이론적으로, 이스케이프 분석을 사용하여 GC를 피할 수있는 경우를 찾을 수 있습니다. 실제로 다음과 같은 간단한 메소드 체인foo().bar().baz() 힙에 많은 수의 수명이 짧은 객체를 할당해야합니다 (세대 GC는이 문제를 작게 유지하는 한 가지 방법입니다).

    다른 경우, 프로그래머는 이미 목록과 같은 일부 객체의 최종 크기를 알고있을 수 있습니다. 불행히도 파이썬은 새 목록을 만들 때 이것을 전달하는 방법을 제공하지 않습니다. 대신 새 항목이 끝까지 푸시되므로 여러 재 할당이 필요할 수 있습니다. 몇 가지 참고 사항 :

    • 특정 크기의 목록은 다음과 같이 만들 수 있습니다 fixed_size = [None] * size. 그러나 해당 목록 내의 개체에 대한 메모리는 별도로 할당해야합니다. 우리가 할 수있는 대조적 인 C ++ std::array<Type, size> fixed_size.

    • 파이썬은 array내장 모듈을 통해 특정 고유 유형의 묶음 배열을 만들 수 있습니다 . 또한 numpy기본 숫자 유형에 대한 특정 모양의 데이터 버퍼를 효율적으로 표시합니다.

요약

파이썬은 성능이 아니라 사용하기 쉽도록 설계되었습니다. 그것의 디자인은 매우 효율적인 구현을 만드는 것을 다소 어렵게 만듭니다. 프로그래머가 문제가있는 기능을 사용하지 않으면 나머지 관용구를 이해하는 컴파일러는 C 성능과 비교할 수있는 효율적인 코드를 생성 할 수 있습니다.


8

예. 가장 큰 문제는 언어가 동적 인 언어로 정의되어 있다는 것입니다. 따라서 머신 코드를 생성 할 대상을 모르기 때문에 효율적인 머신 코드를 생성하기가 매우 어렵 습니다 . JIT 컴파일러는 이 분야에서 약간의 작업을 수행 할 수 있지만 JIT 컴파일러는 단순히 시간과 메모리를 소비 할 수 없기 때문에 결코 C ++과 비교할 수 없습니다. 시간과 메모리는 프로그램을 실행하는 데 소비하지 않는 시간과 메모리이기 때문에 동적 언어 시맨틱을 깨지 않고도 달성 할 수 있습니다.

나는 이것이 받아 들일 수없는 절충이라고 주장하지 않을 것입니다. 그러나 실제 구현이 C ++ 구현만큼 빠르지 않다는 것이 Python의 본질에 기본입니다.


8

모든 동적 언어의 성능에 영향을 미치는 주요 요인은 세 가지가 있습니다.

  1. 해석상의 오버 헤드. 런타임에는 기계 명령어가 아닌 일종의 바이트 코드가 있으며이 코드를 실행하는 데 고정 된 오버 헤드가 있습니다.
  2. 디스패치 오버 헤드. 함수 호출의 대상은 런타임까지 알려지지 않았으며 호출 할 메소드를 찾는 데 비용이 듭니다.
  3. 메모리 관리 오버 헤드. 동적 언어는 할당 및 할당 해제해야하고 성능 오버 헤드가있는 개체에 물건을 저장합니다.

C / C ++의 경우이 세 가지 요소의 상대적 비용은 거의 0입니다. 명령어는 프로세서에 의해 직접 실행되며 디스패치에는 최대 간접 지정이 걸리며, 그렇지 않으면 힙 메모리는 할당되지 않습니다. 잘 작성된 코드는 어셈블리 언어에 접근 할 수 있습니다.

JIT 컴파일을 사용하는 C # / Java의 경우 처음 두 개는 낮지 만 가비지 수집 메모리에는 비용이 있습니다. 잘 작성된 코드는 2x C / C ++에 접근 할 수 있습니다.

Python / Ruby / Perl의 경우이 세 가지 요소 모두의 비용이 상대적으로 높습니다. C / C ++에 비해 5 배 이상이라고 생각하십시오. (*)

런타임 라이브러리 코드는 프로그램과 동일한 언어로 작성 될 수 있으며 동일한 성능 제한이 있습니다.


(*) JIT (Just-In_Time) 컴파일은 이러한 언어로 확장되므로 잘 작성된 C / C ++ 코드의 속도에 도달합니다 (일반적으로 2 배).

또한 경쟁 언어간에 차이가 좁아지면 알고리즘과 구현 세부 사항에 따라 차이가 지배적이라는 점에 유의해야합니다. JIT 코드는 C / C ++를 이길 수 있고 C / C ++는 좋은 코드를 작성하는 것이 더 쉽기 때문에 어셈블리 언어를 이길 수 있습니다.


"런타임 라이브러리 코드는 프로그램과 동일한 언어로 작성 될 수 있으며 동일한 성능 제한이있을 수 있습니다." "Python / Ruby / Perl의 경우이 세 가지 요소 모두의 비용이 상대적으로 높습니다. C / C ++에 비해 5 배나 더 나쁜 것으로 생각하십시오." 사실, 그것은 사실이 아닙니다. 예를 들어, Rubinius Hash클래스 (Ruby의 핵심 데이터 구조 중 하나)는 Ruby Hash로 작성되며 C로 작성된 YARV의 클래스 보다 성능이 훨씬 빠르며 때로는 더 빠릅니다 . 그 이유 중 하나는 Rubinius 런타임의 많은 부분입니다. 시스템은 루비로 작성되었으므로 다음을 수행 할 수 있습니다.
Jörg W Mittag

… 예를 들어 Rubinius 컴파일러에 의해 인라인됩니다. 극단적 인 예입니다 클라인 VM (자기에 대한 metacircular VM)과 맥신 VM (자바에 대한 metacircular VM), 모든 것을 , 심지어 방법 파견 코드, 가비지 컬렉터, 메모리 할당, 기본 유형, 핵심 데이터 구조체와 알고리즘이 작성됩니다 자체 또는 Java. 이렇게하면 핵심 VM의 일부라도 사용자 코드에 인라인 할 수 있으며 VM은 사용자 프로그램의 런타임 피드백을 사용하여 자체 컴파일 및 재 최적화 할 수 있습니다.
Jörg W Mittag

@ JörgWMittag : 여전히 그렇습니다. Rubinius에는 JIT가 있으며 JIT 코드는 종종 개별 벤치 마크에서 C / C ++를 능가합니다. JIT가 없을 때이 메타 서클이 속도에 큰 영향을 미친다는 증거를 찾을 수 없습니다. [JIT에 대한 명확성을 보려면 편집을 참조하십시오.]
david.pfx

1

그러나 파이썬 스크립트가 동등한 C ++ 프로그램만큼 빠르지 못하게하는 기술적 제한이나 언어 기능이 있습니까?

아니요. C ++을 빠르게 실행하는 데 따르는 돈과 리소스에 대한 문제 일뿐 아니라 Python을 빠르게 실행하는 데 드는 돈과 리소스는 문제입니다.

예를 들어, Self VM이 나왔을 때 가장 빠른 동적 OO 언어 일뿐 아니라 가장 빠른 OO 언어 기간이었습니다. 에도 불구하고 믿을 수 없을만큼 (예를 들어, 더 많은 그래서 파이썬, 루비, PHP 또는 자바 스크립트보다) 동적 언어를 더 빨리 사용할 수 있었던 C의 가장 ++ 구현에 비해이었다.

그러나 Sun은 TV 셋톱 박스의 애니메이션 메뉴를위한 작은 스크립팅 언어에 초점을 맞추기 위해 Self 프로젝트 (대형 시스템 개발을위한 성숙한 범용 OO 언어)를 취소했습니다 (들어 보셨을 것입니다, Java라고 함). 더 많은 자금. 동시에, Intel, IBM, Microsoft, Sun, Metrowerks, HP 등. C ++를 빠르게 만들기 위해 엄청난 양의 돈과 자원을 소비했습니다. CPU 제조업체는 C ++를 빠르게하기 위해 칩에 기능을 추가했습니다. C ++를 빠르게 만들기 위해 운영 체제를 작성하거나 수정했습니다. 따라서 C ++은 빠릅니다.

나는 파이썬에 굉장히 친숙하지 않다. 나는 더 많은 루비 사람이다. 그래서 나는 루비로부터 예제를 제공 할 것이다 . 루비니 우스 루비 구현 Hash에서 클래스 ( dict파이썬 에서 기능과 중요성에 상응하는 )는 100 % 순수 루비로 작성되었다; 그러나 Hash손에 최적화 된 C로 작성된 YARV 의 클래스 보다 우월하고 때로는 경쟁 우위 를 차지합니다. 상용 Lisp 또는 Smalltalk 시스템 (또는 앞서 언급 한 Self VM)과 비교할 때 Rubinius의 컴파일러는 그다지 영리하지 않습니다. .

파이썬에는 느리게 만드는 것은 없습니다. 오늘날의 프로세서와 운영 체제에는 Python을 손상시키는 기능이 있습니다 (예 : 가상 메모리는 가비지 수집 성능이 끔찍한 것으로 알려져 있습니다). C ++에는 도움이되지만 Python에는 도움이되지 않는 기능이 있습니다 (현대 CPU는 너무 비싸기 때문에 캐시 미스를 피하려고 시도합니다. 불행히도 캐시 미스를 피하는 것은 OO와 다형성이있을 때 어렵습니다. 오히려 캐시 비용을 줄여야합니다) Java를 위해 설계된 Azul Vega CPU가이를 수행합니다.)

C ++에서와 같이 Python을 빠르게 만드는 데 많은 비용, 연구 및 리소스를 소비하고 C ++ 에서처럼 Python 프로그램을 빠르게 실행할 수있는 운영 체제를 만드는 데 많은 비용, 연구 및 리소스를 소비하고 C ++에서와 같이 Python 프로그램을 빠르게 실행하는 CPU를 만드는 데 많은 돈, 연구 및 리소스가 있다면 Python이 C ++과 비슷한 성능에 도달 할 수 있다는 것은 의심 할 여지가 없습니다.

우리는 ECMAScript를 사용하여 한 명의 플레이어 만 성능에 대해 진지하게 생각할 때 발생할 수있는 일을 보았습니다. 1 년 안에 모든 주요 공급 업체의 기본 성능이 기본적으로 10 배 향상되었습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.