>  기사  >  백엔드 개발  >  JIT 컴파일러를 사용하여 Python 루프를 느리게 만드시나요?

JIT 컴파일러를 사용하여 Python 루프를 느리게 만드시나요?

PHPz
PHPz원래의
2024-08-29 06:35:07865검색

들어보지 못하셨다면 Python 루프가 느릴 수 있습니다. 특히 대규모 데이터세트로 작업할 때 더욱 그렇습니다. 수백만 개의 데이터 포인트에 걸쳐 계산을 수행하려는 경우 실행 시간이 빠르게 병목 현상을 일으킬 수 있습니다. 다행스럽게도 Numba에는 Python의 수치 계산 및 루프 속도를 높이는 데 사용할 수 있는 JIT(Just-in-Time) 컴파일러가 있습니다.

어느 날 Python에서 간단한 지수평활 함수가 필요하다는 사실을 깨달았습니다. 이 함수는 배열을 가져와서 평활화된 값과 동일한 길이의 배열을 반환해야 했습니다. 일반적으로 저는 Python에서 가능한 한 루프를 피하려고 노력합니다(특히 Pandas DataFrames를 다룰 때). 현재 능력 수준에서는 루프를 사용하여 값 배열을 기하급수적으로 평활화하는 방법을 알지 못했습니다.

이 지수평활 함수를 생성하고 JIT 컴파일 유무에 관계없이 테스트하는 과정을 살펴보겠습니다. JIT에 대해 간단히 설명하고 nopython 모드에서 작동하는 방식으로 루프를 코딩하는 방법을 설명하겠습니다.

JIT란 무엇입니까?

JIT 컴파일러는 Python, JavaScript, Java와 같은 고급 언어에 특히 유용합니다. 이러한 언어는 유연성과 사용 용이성으로 잘 알려져 있지만 C나 C++와 같은 하위 수준 언어에 비해 실행 속도가 느려질 수 있습니다. JIT 컴파일은 런타임 시 코드 실행을 최적화하여 이러한 고급 언어의 장점을 희생하지 않고도 코드 실행 속도를 높여 이러한 격차를 해소하는 데 도움이 됩니다.

Numba JIT 컴파일러에서 nopython=True 모드를 사용하면 Python 인터프리터가 완전히 우회되어 Numba가 모든 것을 기계어 코드로 컴파일해야 합니다. 그 결과 Python의 동적 타이핑 및 기타 인터프리터 관련 작업과 관련된 오버헤드가 제거되어 실행 속도가 더욱 빨라졌습니다.

빠른 지수 평활 기능 구축

지수 평활은 과거 관측치에 가중 평균을 적용하여 데이터를 평활화하는 데 사용되는 기술입니다. 지수평활 공식은 다음과 같습니다.

t=αVt+(1α)t1 S_t = 알파 cdot V_t + (1 - 알파) cdot S_{t-1} t=α⋅ t+(1−α)⋅St−1

장소:

  • t : 당시의 평활화된 값을 나타냅니다. ttt .
  • VtV_tt : 당시의 원래 값을 나타냅니다. ttt 값 배열에서.
  • α알파α : 현재 값의 가중치를 결정하는 평활화 요소 VtV_tt 평활화 과정에서.
  • t1S_{t-1}St−1 : 당시의 평활화된 값을 나타냅니다. t1t-1t−1 즉, 이전에 평활화된 값입니다.

공식은 지수 평활을 적용합니다. 여기서:

  • 새로 평활화된 값 t 현재 값의 가중 평균입니다. VtV_tt 이전에 평활화된 값 t1S_{t-1}St−1 .
  • 요인 α알파α 현재 값이 얼마나 영향을 미치는지 결정합니다. VtV_tt 이전 평활화된 값과 비교하여 평활화된 값을 갖습니다. t1S_{t-1}St−1 .

이를 Python에서 구현하고 nopython=True 모드에서 작동하는 기능을 고수하기 위해 데이터 값 배열과 알파 부동 소수점을 전달합니다. 현재 사용 사례에 적합하기 때문에 기본적으로 알파 값은 0.33333333입니다. 평활화된 값을 저장하고, 반복하고, 계산하고, 평활화된 값을 반환하기 위해 빈 배열을 초기화하겠습니다. 다음과 같습니다.

@jit(nopython=True) 
def fast_exponential_smoothing(values, alpha=0.33333333): 

    smoothed_values = np.zeros_like(values) # Array of zeros the same length as values
    smoothed_values[0] = values[0] # Initialize the first value 

    for i in range(1, len(values)): 
        smoothed_values[i] = alpha * values[i] + (1 - alpha) * smoothed_values[i - 1]
    return smoothed_values

간단하죠? JIT가 지금 무엇을 하고 있는지 살펴보겠습니다. 먼저, 큰 정수 배열을 만들어야 합니다. 그런 다음 함수를 호출하고 계산하는 데 걸린 시간을 측정한 후 결과를 인쇄합니다.

# Generate a large random array of a million integers
large_array = np.random.randint(1, 100, size=1_000_000)

# Test the speed of fast_exponential_smoothing
start_time = time.time()
smoothed_result = fast_exponential_smoothing(large_array)
end_time = time.time()
print(f"Exponential Smoothing with JIT took {end_time - start_time:.6f} seconds with 1,000,000 sample array.")

This can be repeated and altered just a bit to test the function without the JIT decorator. Here are the results that I got:

Using JIT-compilers to make my Python loops slower?

Wait, what the f***?

I thought JIT was supposed to speed it up. It looks like the standard Python function beat the JIT version and a version that attempts to use no recursion. That's strange. I guess you can't just slap the JIT decorator on something and make it go faster? Perhaps simple array loops and NumPy operations are already pretty efficient? Perhaps I don't understand the use case for JIT as well as I should? Maybe we should try this on a more complex loop?

Here is the entire code python file I created for testing:

import numpy as np
from numba import jit
import time

@jit(nopython=True) 
def fast_exponential_smoothing(values, alpha=0.33333333): 

    smoothed_values = np.zeros_like(values) # Array of zeros the same length as values
    smoothed_values[0] = values[0] # Initialize the first value 

    for i in range(1, len(values)): 
        smoothed_values[i] = alpha * values[i] + (1 - alpha) * smoothed_values[i - 1]
        return smoothed_values

def fast_exponential_smoothing_nojit(values, alpha=0.33333333):

    smoothed_values = np.zeros_like(values) # Array of zeros the same length as values
    smoothed_values[0] = values[0] # Initialize the first value 

    for i in range(1, len(values)): 
        smoothed_values[i] = alpha * values[i] + (1 - alpha) * smoothed_values[i - 1]
        return smoothed_values

def non_recursive_exponential_smoothing(values, alpha=0.33333333):
    n = len(values)
    smoothed_values = np.zeros(n)

    # Initialize the first value
    smoothed_values[0] = values[0]

    # Calculate the rest of the smoothed values
    decay_factors = (1 - alpha) ** np.arange(1, n)
    cumulative_weights = alpha * decay_factors
    smoothed_values[1:] = np.cumsum(values[1:] * np.flip(cumulative_weights)) + (1 - alpha) ** np.arange(1, n) * values[0]

    return smoothed_values

# Generate a large random array of a million integers
large_array = np.random.randint(1, 1000, size=10_000_000)

# Test the speed of fast_exponential_smoothing
start_time = time.time()
smoothed_result = fast_exponential_smoothing_nojit(large_array)
end_time = time.time()
print(f"Exponential Smoothing without JIT took {end_time - start_time:.6f} seconds with 1,000,000 sample array.")

# Test the speed of fast_exponential_smoothing
start_time = time.time()
smoothed_result = fast_exponential_smoothing(large_array)
end_time = time.time()
print(f"Exponential Smoothing with JIT took {end_time - start_time:.6f} seconds with 1,000,000 sample array.")

# Test the speed of fast_exponential_smoothing
start_time = time.time()
smoothed_result = non_recursive_exponential_smoothing(large_array)
end_time = time.time()
print(f"Exponential Smoothing with no recursion or JIT took {end_time - start_time:.6f} seconds with 1,000,000 sample array.")

I attempted to create the non-recursive version to see if vectorized operations across arrays would make it go faster, but it seems to be pretty damn fast as it is. These results remained the same all the way up until I didn't have enough memory to make the array of random integers.

Let me know what you think about this in the comments. I am by no means a professional developer, so I am accepting all comments, criticisms, or educational opportunities.

Until next time.

Happy coding!

위 내용은 JIT 컴파일러를 사용하여 Python 루프를 느리게 만드시나요?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.