복붙노트

[PYTHON] numpy float : 산술 연산에 내장 된 것보다 10 배 느린가?

PYTHON

numpy float : 산술 연산에 내장 된 것보다 10 배 느린가?

나는 다음 코드에 대해 정말 이상한 타이밍을 얻고있다 :

import numpy as np
s = 0
for i in range(10000000):
    s += np.float64(1) # replace with np.float32 and built-in float

float64가 float보다 두 배 느린 이유는 무엇입니까? 그리고 float32가 float64보다 5 배 느린 이유는 무엇입니까?

np.float64를 사용했을 때 벌칙을 피할 수있는 방법이 있습니까? 그리고 numpy 함수는 float64 대신 내장 float를 반환합니까?

나는 numpy.float64를 사용하는 것이 파이썬의 부동 소수점보다 훨씬 느리고 numpy.float32는 더 느리다는 것을 발견했다. (32 비트 컴퓨터에서도).

numpy.float32 내 32 비트 컴퓨터에서. 따라서 numpy.random.uniform과 같은 다양한 numpy 함수를 사용할 때마다 결과를 float32로 변환하므로 (32 비트 정밀도로 이후 작업이 수행됨)

프로그램이나 명령 줄에서 어딘가에 단일 변수를 설정하고 모든 numpy 함수가 float64 대신 float32를 반환하도록하는 방법이 있습니까?

편집 # 1 :

numpy.float64는 산술 계산에서 float보다 10 배 느립니다. 계산하기 전에 float과 back로 변환하는 것조차도 프로그램을 3 배 빠르게 실행하는 것은 그리 좋지 않습니다. 왜? 문제를 해결하기 위해 할 수있는 일이 있습니까?

나는 나의 타이밍이 다음에 의한 것이 아니라고 강조하고 싶다.

문제가있는 곳을 명확하게하기 위해 코드를 업데이트했습니다. 새로운 코드를 사용하면 numpy 데이터 형식을 사용할 때보 다 10 배의 성능이 향상되는 것으로 나타났습니다.

from datetime import datetime
import numpy as np

START_TIME = datetime.now()

# one of the following lines is uncommented before execution
#s = np.float64(1)
#s = np.float32(1)
#s = 1.0

for i in range(10000000):
    s = (s + 8) * s % 2399232

print(s)
print('Runtime:', datetime.now() - START_TIME)

타이밍은 다음과 같습니다.

그냥 지옥에 대한, 나는 또한 시도 :

datetime에서 가져 오기 datetime npy로 numpy 가져 오기

START_TIME = datetime.now()

s = np.float64(1)
for i in range(10000000):
    s = float(s)
    s = (s + 8) * s % 2399232
    s = np.float64(s)

print(s)
print('Runtime:', datetime.now() - START_TIME)

실행 시간은 13.28 초입니다. 실제로 float64를 사용하는 것보다 float64를 float 및 back으로 변환하는 것이 실제로 3 배 빠릅니다. 그럼에도 불구하고 변환 작업은 많은 비용을 필요로하므로 전반적으로 pure-python float보다 3 배 이상 느립니다.

내 컴퓨터는 다음과 같습니다.

편집 # 2 :

답변을 주셔서 감사합니다, 그들은이 문제를 해결하는 방법을 이해하는 데 도움이됩니다.

그러나 나는 여전히 플로트보다 float64로 10 배 느리게 실행되는 정확한 이유 (아마도 소스 코드를 기반으로)를 알고 싶습니다.

편집 # 3 :

Windows 7 x64 (Intel Core i7 930 @ 3.8GHz)에서 코드를 다시 실행합니다.

다시 말하지만 코드는 다음과 같습니다.

from datetime import datetime
import numpy as np

START_TIME = datetime.now()

# one of the following lines is uncommented before execution
#s = np.float64(1)
#s = np.float32(1)
#s = 1.0

for i in range(10000000):
    s = (s + 8) * s % 2399232

print(s)
print('Runtime:', datetime.now() - START_TIME)

타이밍은 다음과 같습니다.

이제 두 np 플로트 (64 또는 32)는 내장 플로트보다 5 배 느립니다. 여전히 중요한 차이입니다. 나는 그것이 어디서 왔는지 알아 내려고 노력하고있다.

편집 종료

해결법

  1. ==============================

    1.CPython 수레는 청크로 할당됩니다.

    CPython 수레는 청크로 할당됩니다.

    numpy 스칼라 할당을 float 유형과 비교할 때의 주요 문제점은 CPython이 항상 크기가 N 인 블록에서 float 및 int 객체에 대한 메모리를 할당한다는 것입니다.

    내부적으로, CPython은 N 개의 float 객체를 수용 할 수있을만큼 큰 블록들의 링크 된 목록을 관리합니다. float (1)을 호출하면 CPython은 현재 블록에서 사용 가능한 공간이 있는지 확인합니다. 그렇지 않으면 새로운 블록을 할당합니다. 현재 블록에 공간이 있으면 공간을 초기화하고 포인터를 반환합니다.

    내 컴퓨터에서 각 블록은 41 개의 부동 개체를 저장할 수 있으므로 첫 번째 float (1) 호출에 약간의 오버 헤드가 있지만 다음 40 개는 메모리가 할당되어 준비 될 때 훨씬 빠르게 실행됩니다.

    천천히 numpy.float32 대 numpy.float64

    numpy는 스칼라 유형을 만들 때 취할 수있는 두 가지 경로, 즉 빠르거나 느린 것으로 나타납니다. 이는 스칼라 유형이 인수 변환을 연기 할 수있는 Python 기본 클래스를 가지고 있는지 여부에 달려 있습니다.

    numpy.float32는 (_WORK0 매크로로 정의 된) 더 느린 경로를 사용하기 위해 하드 코딩 된 반면, numpy.float64는 _WORK1 매크로로 정의 된 더 빠른 경로를 사용할 기회를 얻습니다. scalartypes.c.src는 빌드 할 때 scalartypes.c를 생성하는 템플릿입니다.

    이것을 Cachegrind에서 시각화 할 수 있습니다. float32와 float64를 구성하는 데 더 많은 호출이 이루어지는 것을 보여주는 화면 캡처를 포함했습니다.

    float64는 빠른 경로를 사용합니다.

    float32는 느린 경로를 사용합니다.

    업데이트 됨 - 느린 / 빠른 경로를 사용하는 유형은 OS가 32 비트 대 64 비트인지에 따라 달라질 수 있습니다. 내 테스트 시스템에서 Ubuntu Lucid 64 비트, float64 유형은 float32보다 10 배 빠릅니다.

  2. ==============================

    2.이런 무거운 루프에서 파이썬 객체로 작동하는 것은 float인지, np.float32인지에 관계없이 항상 느립니다. NumPy는 벡터와 행렬에 대한 연산이 빠르다. 왜냐하면 모든 연산이 파이썬 인터프리터가 아니라 C로 작성된 라이브러리의 일부분에 의해 큰 데이터 청크에서 수행되기 때문이다. 인터프리터에서 실행되는 코드 및 / 또는 Python 객체를 사용하는 코드는 항상 느리며 비원시 유형을 사용하면 속도가 더 느려집니다. 그것은 예상된다.

    이런 무거운 루프에서 파이썬 객체로 작동하는 것은 float인지, np.float32인지에 관계없이 항상 느립니다. NumPy는 벡터와 행렬에 대한 연산이 빠르다. 왜냐하면 모든 연산이 파이썬 인터프리터가 아니라 C로 작성된 라이브러리의 일부분에 의해 큰 데이터 청크에서 수행되기 때문이다. 인터프리터에서 실행되는 코드 및 / 또는 Python 객체를 사용하는 코드는 항상 느리며 비원시 유형을 사용하면 속도가 더 느려집니다. 그것은 예상된다.

    앱이 느리고 최적화가 필요한 경우 코드를 NumPy를 직접 사용하는 벡터 솔루션으로 변환하는 것이 빠르고 빠르거나 Cython과 같은 도구를 사용하여 C에서 빠른 루프 구현을 만들 수 있습니다. .

  3. ==============================

    3.아마도 루프를 사용하는 대신 Numpy를 직접 사용해야하는 이유 일 수 있습니다.

    아마도 루프를 사용하는 대신 Numpy를 직접 사용해야하는 이유 일 수 있습니다.

    s1 = np.ones(10000000, dtype=np.float)
    s2 = np.ones(10000000, dtype=np.float32)
    s3 = np.ones(10000000, dtype=np.float64)
    
    np.sum(s1) <-- 17.3 ms
    np.sum(s2) <-- 15.8 ms
    np.sum(s3) <-- 17.3 ms
    
  4. ==============================

    4.대답은 아주 간단합니다. 메모리 할당이 그 일부일 수도 있지만 가장 큰 문제는 numpy 스칼라에 대한 산술 연산이 "ufuncs"를 사용하여 수행된다는 것입니다.이 ufuncs는 1이 아닌 수백 개의 값에 대해 빠르다는 의미입니다. 루프를 호출하고 설정하는 올바른 기능을 선택하십시오. 스칼라에 필요하지 않은 오버 헤드.

    대답은 아주 간단합니다. 메모리 할당이 그 일부일 수도 있지만 가장 큰 문제는 numpy 스칼라에 대한 산술 연산이 "ufuncs"를 사용하여 수행된다는 것입니다.이 ufuncs는 1이 아닌 수백 개의 값에 대해 빠르다는 의미입니다. 루프를 호출하고 설정하는 올바른 기능을 선택하십시오. 스칼라에 필요하지 않은 오버 헤드.

    스칼라를 0-d 배열로 변환 한 다음 해당 numpy ufunc로 전달한 다음 NumPy가 지원하는 다양한 스칼라 유형 각각에 대해 별도의 계산 방법을 작성하는 것이 더 쉬웠습니다.

    그 의도는 스칼라 수학의 최적화 된 버전이 C의 타입 객체에 추가 될 것이라는 것이 었습니다. 이것은 여전히 ​​일어날 수 있지만 아무도 그렇게 할 동기가 없기 때문에 결코 발생하지 않았습니다. 해결 방법은 numpy 스칼라를 최적화 된 산술 연산을 수행하는 파이썬 스칼라로 변환하기 때문일 수 있습니다.

  5. ==============================

    5.개요

    개요

    산술 표현식에 숫자와 내장 숫자가 모두 포함되어 있으면 파이썬 연산 속도가 느려집니다. 이 변환을 피하면 내가보고 한 거의 모든 성능 저하가 제거됩니다.

    세부

    내 원래 코드에서 유의하십시오.

    s = np.float64(1)
    for i in range(10000000):
      s = (s + 8) * s % 2399232
    

    float 및 numpy.float64 유형이 하나의 표현식에 혼합되어 있습니다. 아마도 파이썬을 모두 하나의 유형으로 변환해야만했을까요?

    s = np.float64(1)
    for i in range(10000000):
      s = (s + np.float64(8)) * s % np.float64(2399232)
    

    런타임이 변경되지 않은 경우 (증가하지 않고), 이는 실제로 파이썬이 성능 저하를 설명하면서 실제로 수행하고 있었던 것입니다.

    사실, 런타임은 1.5 배 떨어졌습니다! 그게 어떻게 가능해? 파이썬이 할 수있는 최악의 상황은이 두 가지 변환이 아닌가?

    나는 정말로 모른다. 아마도 파이썬은 무엇이 무엇으로 변환되어야 하는지를 동적으로 확인해야했기 때문에 시간이 걸리고 수행 할 정확한 변환을 통해 더 빨리 수행 할 수 있습니다. 아마도 전혀 다른 메커니즘이 산술에 사용됩니다 (변환을 전혀 포함하지 않음). 일치하지 않는 유형의 경우에는 매우 느립니다. numpy 소스 코드를 읽는 것이 도움이 될 수 있지만, 필자의 기술을 뛰어 넘습니다.

    어쨌든 이제 루프에서 전환을 이동하여 속도를 높일 수 있습니다.

    q = np.float64(8)
    r = np.float64(2399232)
    for i in range(10000000):
      s = (s + q) * s % r
    

    예상대로 런타임이 크게 줄어 들었습니다 : 2.3 배.

    공정하게하기 위해서 우리는 이제 루프에서 리터럴 상수를 이동 시켜서 float 버전을 약간 변경해야합니다. 이로 인해 작은 (10 %) 속도 저하가 발생합니다.

    이러한 모든 변경 사항을 고려하면 np.float64 버전의 코드는 현재 동일한 버전의 플로트 버전보다 30 % 더 느립니다. 우스운 5 배의 성능 히트는 대부분 사라졌습니다.

    왜 우리는 여전히 30 % 지연을 볼 수 있습니까? numpy.float64 숫자는 float와 동일한 양의 공간을 차지하므로 이유가되지 않습니다. 아마도 산술 연산자의 해상도는 사용자 정의 형식에 더 오래 걸릴 것입니다. 확실히 중요한 관심사가 아닙니다.

  6. ==============================

    6.빠른 스칼라 산술 연산을 수행했다면 numpy보다는 gmpy 라이브러리를 사용해야한다 (다른 것들은 언급했듯이, 후자는 스칼라 연산보다는 벡터 연산에 더 최적화되어있다).

    빠른 스칼라 산술 연산을 수행했다면 numpy보다는 gmpy 라이브러리를 사용해야한다 (다른 것들은 언급했듯이, 후자는 스칼라 연산보다는 벡터 연산에 더 최적화되어있다).

  7. ==============================

    7.결과도 확인할 수 있습니다. 나는 모든 numpy 타입을 사용하는 것이 어떻게 보이는지 보려고했는데, 그 차이는 계속됩니다. 그럼, 내 테스트는 다음과 같습니다 :

    결과도 확인할 수 있습니다. 나는 모든 numpy 타입을 사용하는 것이 어떻게 보이는지 보려고했는데, 그 차이는 계속됩니다. 그럼, 내 테스트는 다음과 같습니다 :

    def testStandard(length=100000):
        s = 1.0
        addend = 8.0
        modulo = 2399232.0
        startTime = datetime.now()
        for i in xrange(length):
            s = (s + addend) * s % modulo
        return datetime.now() - startTime
    
    def testNumpy(length=100000):
        s = np.float64(1.0)
        addend = np.float64(8.0)
        modulo = np.float64(2399232.0)
        startTime = datetime.now()
        for i in xrange(length):
            s = (s + addend) * s % modulo
        return datetime.now() - startTime
    

    따라서이 시점에서 numpy 유형은 모두 서로 상호 작용하지만 10x 차이는 지속됩니다 (2 초 vs. 0.2 초).

    추측해야만한다면 기본 부동 소수점 형식이 왜 더 빠른지에 대한 두 가지 이유가있을 수 있습니다. 첫 번째 가능성은 특정 수치 연산 또는 일반적으로 루핑 (예 : 루프 언 롤링)을 처리하기 위해 Python이 후드에서 중요한 최적화를 수행한다는 것입니다. 두 번째 가능성은 numpy 유형이 추상화의 추가 레이어를 포함한다는 것입니다 (즉, 주소에서 읽어야 함). 각각의 효과를 조사하기 위해 몇 가지 추가 검사를 수행했습니다.

    한 가지 차이점은 파이썬이 float64 유형을 해결하기 위해 추가 조치를 취해야하는 결과 일 수 있습니다. 효율적인 테이블을 생성하는 컴파일 된 언어와 달리 Python 2.6 (및 어쩌면 3)은 일반적으로 무료라고 생각하는 것을 해결하는 데 상당한 비용이 듭니다. 간단한 X.a 해상도조차 도트 연산자가 호출 될 때마다이를 해결해야합니다. 따라서 instance.function ()을 호출하는 루프가 있으면 루프 외부에서 선언 된 "function = instance.function"변수가 있어야합니다.

    필자가 알기로는 파이썬 표준 연산자를 사용할 때 "import operator"의 연산자를 사용하는 것과 매우 유사합니다. +, * 및 % 대신에 add, mul 및 mod를 사용하면 표준 연산자와 비교하여 약 0.5 초의 정적 성능이 나타납니다 (두 경우 모두). 즉, 연산자를 래핑하면 표준 파이썬 부동 소수점 연산이 3 배 느려집니다. 추가 작업을 수행하는 경우 operator.add와 그 변형을 사용하여 약 0.7 초 (약 2 초 및 0.2 초 각각 1m 재판)가 추가됩니다. 5 배의 느린 속도입니다. 기본적으로 이러한 문제가 각각 두 번 발생하면 기본적으로 느린 10 배가됩니다.

    그럼 우리는 잠시 파이썬 인터프리터라고 가정 해 봅시다. 사례 1, 우리는 네이티브 타입에 대한 연산을 수행하고, a + b라고 가정 해 봅시다. 후드에서 a 및 b의 유형을 확인하고 파이썬의 최적화 된 코드에 추가 할 수 있습니다. 사례 2, 우리는 두 가지 다른 유형의 연산 (a + b)을 수행합니다. 후드에서 기본 유형인지 확인합니다 (그렇지 않은 경우). 우리는 '다른'경우로 이동합니다. else case는 우리에게 a.add (b)와 같은 것을 보냅니다. 그런 다음 a.add는 numpy의 최적화 된 코드로 디스패치를 ​​수행 할 수 있습니다. 그래서이 시점에서 여분의 브랜치, 즉 하나의 추가적인 오버 헤드가있었습니다. 슬롯 속성 및 함수 호출 얻기. 그리고 우리는 단지 더하기 연산을 수행했습니다. 그런 다음 결과를 사용하여 새 float64를 만들거나 기존 float64를 변경해야합니다. 한편, 파이썬 네이티브 코드는 이런 유형의 오버 헤드를 피하기 위해 해당 유형을 특별히 처리함으로써 속임수를 사용합니다.

    위의 파이썬 함수 호출과 범위 오버 헤드의 비용이 많이 드는 것에 대한 시험을 토대로 numpy가 c 수학 함수를오고가는 9x 패널티를받는 것은 꽤 쉽습니다. 나는이 과정이 단순한 수학 연산 호출보다 오랜 시간이 걸린다는 것을 완전히 상상할 수 있습니다. 각 작업에 대해 numpy 라이브러리는 C 구현을 위해 파이썬 레이어를 따라야합니다.

    그래서 제 의견으로는, 그 이유는 아마이 효과에 잡혔을 것입니다 :

    length = 10000000
    class A():
        X = 10
    startTime = datetime.now()
    for i in xrange(length):
        x = A.X
    print "Long Way", datetime.now() - startTime
    startTime = datetime.now()
    y = A.X
    for i in xrange(length):
        x = y
    print "Short Way", datetime.now() - startTime
    

    이 간단한 사례는 0.2 초와 0.14 초 사이의 차이를 보여줍니다 (짧은 속도, 분명히). 내 생각에 당신이보고있는 것은 주로 합쳐진 그 덩어리들입니다.

    이를 피하기 위해, 나는 주로 말한 것을 반복하는 몇 가지 가능한 해결책을 생각해 볼 수 있습니다. 첫 번째 해결책은 가능한 한 NumPy 내부에서 평가를 유지하는 것입니다. 많은 양의 손실은 아마도 인터페이스 때문일 것입니다. 나는 numpy 또는 C로 최적화 된 다른 숫자 라이브러리에 작업을 파견하는 방법을 살펴볼 것입니다 (gmpy가 언급되었습니다). 목표는 최대한 많은 시간을 C로 밀어 넣은 다음 결과를 다시 얻는 것입니다. 당신은 큰 직업에 종사하고 싶지, 작은 직업을 많이 갖기를 원치 않습니다.

    물론 두 번째 해결책은 가능한 경우 Python으로 중급 및 소규모 작업을 더 많이 수행하는 것입니다. 분명히 네이티브 객체를 사용하는 것이 더 빠를 것입니다. 그것들은 모든 브랜치 문에서 첫 번째 옵션이 될 것이며 항상 C 코드에 대한 최단 경로를 갖게 될 것입니다. 고정 소수점 연산이나 기본 연산자에 대한 다른 문제에 대한 특별한 필요가 없다면 왜 많은 것들에 대해 직선형 파이썬 함수를 사용하지 않는지 알 수 없습니다.

  8. ==============================

    8.정말 이상한 ... 나는 우분투 11.04 32 비트, 파이썬 2.7.1, numpy 1.5.1 (공식 패키지)의 결과를 확인합니다 :

    정말 이상한 ... 나는 우분투 11.04 32 비트, 파이썬 2.7.1, numpy 1.5.1 (공식 패키지)의 결과를 확인합니다 :

    import numpy as np
    def testfloat():
        s = 0
        for i in range(10000000):  
            s+= float(1)
    def testfloat32():
        s = 0
        for i in range(10000000):  
            s+= np.float32(1)
    def testfloat64():
        s = 0
        for i in range(10000000):  
            s+= np.float64(1)
    
    %time testfloat()
    CPU times: user 4.66 s, sys: 0.06 s, total: 4.73 s
    Wall time: 4.74 s
    
    %time testfloat64()
    CPU times: user 11.43 s, sys: 0.07 s, total: 11.50 s
    Wall time: 11.57 s
    
    
    %time testfloat32()
    CPU times: user 47.99 s, sys: 0.09 s, total: 48.08 s
    Wall time: 48.23 s
    

    나는 왜 float32가 float64보다 5 배 더 느려야하는지 보지 못합니다.

  9. from https://stackoverflow.com/questions/5956783/numpy-float-10x-slower-than-builtin-in-arithmetic-operations by cc-by-sa and MIT license