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.
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.
Pelicinan eksponen ialah teknik yang digunakan untuk melicinkan data dengan menggunakan purata wajaran berbanding pemerhatian lalu. Formula untuk pelicinan eksponen ialah:
di mana:
Formula menggunakan pelicinan eksponen, di mana:
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:
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!