Rumah  >  Artikel  >  pembangunan bahagian belakang  >  Menggunakan JIT-compiler untuk membuat gelung Python saya lebih perlahan?

Menggunakan JIT-compiler untuk membuat gelung Python saya lebih perlahan?

PHPz
PHPzasal
2024-08-29 06:35:07865semak imbas

Jika anda tidak pernah mendengar, gelung Python boleh menjadi perlahan--terutamanya apabila bekerja dengan set data yang besar. Jika anda cuba membuat pengiraan merentas berjuta-juta titik data, masa pelaksanaan boleh menjadi halangan dengan cepat. Nasib baik bagi kami, Numba mempunyai pengkompil Just-in-Time (JIT) yang boleh kami gunakan untuk membantu mempercepatkan pengiraan berangka dan gelung kami dalam Python.

Pada hari yang lain, saya mendapati diri saya memerlukan fungsi pelicinan eksponen yang mudah dalam Python. Fungsi ini diperlukan untuk mengambil tatasusunan dan mengembalikan tatasusunan dengan panjang yang sama dengan nilai terlicin. Biasanya, saya cuba dan mengelakkan gelung jika boleh dalam Python (terutamanya apabila berurusan dengan Pandas DataFrames). Pada tahap keupayaan semasa saya, saya tidak nampak cara untuk mengelak daripada menggunakan gelung untuk melicinkan susunan nilai secara eksponen.

Saya akan meneruskan proses mencipta fungsi pelicinan eksponen ini dan mengujinya dengan dan tanpa kompilasi JIT. Saya akan menyentuh secara ringkas tentang JIT dan cara saya memastikan untuk mengekodkan gelung dengan cara yang berfungsi dengan mod nopython.

Apa itu JIT?

Penyusun JIT amat berguna dengan bahasa peringkat tinggi seperti Python, JavaScript dan Java. Bahasa ini terkenal dengan fleksibiliti dan kemudahan penggunaannya, tetapi mereka boleh mengalami kelajuan pelaksanaan yang lebih perlahan berbanding bahasa peringkat rendah seperti C atau C++. Penyusunan JIT membantu merapatkan jurang ini dengan mengoptimumkan pelaksanaan kod pada masa jalan, menjadikannya lebih pantas tanpa mengorbankan kelebihan bahasa peringkat tinggi ini.

Apabila menggunakan mod nopython=True dalam pengkompil Numba JIT, penterjemah Python dipintas sepenuhnya, memaksa Numba untuk menyusun semuanya ke kod mesin. Ini menghasilkan pelaksanaan yang lebih pantas dengan menghapuskan overhed yang dikaitkan dengan penaipan dinamik Python dan operasi berkaitan penterjemah lain.

Membina fungsi pelicinan eksponen yang pantas

Pelicinan eksponen ialah teknik yang digunakan untuk melicinkan data dengan menggunakan purata wajaran berbanding pemerhatian lalu. Formula untuk pelicinan eksponen ialah:

St=αVt+(1α)St1 S_t = alpha cdot V_t + (1 - alpha) cdot S_{t-1} St=α⋅V t+(1−α)⋅St−1

di mana:

  • StS_tSt : Mewakili nilai terlicin pada masa ttt .
  • VtV_tVt : Mewakili nilai asal pada masa ttt daripada tatasusunan nilai.
  • αalfaα : Faktor pelicinan, yang menentukan berat nilai semasa VtV_tVt dalam proses melicinkan.
  • St1S_{t-1}St−1 : Mewakili nilai terlicin pada masa t1t-1t−1 , iaitu, nilai terlicin sebelumnya.

Formula menggunakan pelicinan eksponen, di mana:

  • Nilai terlicin baharu StS_tSt ialah purata wajaran nilai semasa VtV_tVt dan nilai terlicin sebelumnya St1S_{t-1}St−1 .
  • Faktornya αalfaα menentukan sejauh mana pengaruh nilai semasa VtV_tVt mempunyai pada nilai terlicin berbanding dengan nilai terlicin sebelumnya St1S_{t-1}St−1 .

Untuk melaksanakan ini dalam Python, dan berpegang pada fungsi yang berfungsi dengan nopython=Mod Benar, kami akan menghantar dalam tatasusunan nilai data dan apungan alfa. Saya lalai alfa kepada 0.33333333 kerana ia sesuai dengan kes penggunaan semasa saya. Kami akan memulakan tatasusunan kosong untuk menyimpan nilai terlicin dalam, gelung dan mengira serta mengembalikan nilai terlicin. Beginilah rupanya:

@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

Mudah, bukan? Mari lihat sama ada JIT sedang melakukan apa-apa sekarang. Pertama, kita perlu mencipta pelbagai integer yang besar. Kemudian, kami memanggil fungsi itu, masa berapa lama masa yang diambil untuk mengira dan mencetak hasilnya.

# 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!

Atas ialah kandungan terperinci Menggunakan JIT-compiler untuk membuat gelung Python saya lebih perlahan?. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn