まだ聞いたことがない方もいるかもしれませんが、Python ループは、特に大規模なデータセットを扱う場合に遅くなる可能性があります。数百万のデータポイントにわたって計算を行おうとすると、実行時間がすぐにボトルネックになる可能性があります。幸いなことに、Numba には、Python での数値計算とループの高速化に役立つジャストインタイム (JIT) コンパイラーが備わっています。
先日、Python で単純な指数平滑化関数が必要であることに気づきました。この関数は配列を受け取り、平滑化された値を含む同じ長さの配列を返す必要がありました。通常、Python では可能な限りループを避けるようにしています (特に Pandas DataFrame を扱う場合)。私の現在の能力レベルでは、値の配列を指数関数的に平滑化するループの使用を回避する方法が分かりませんでした。
この指数平滑化関数を作成し、JIT コンパイルを使用した場合と使用しない場合のテストのプロセスを順を追って説明します。 JIT と、nopython モードで動作する方法でループをコーディングする方法について簡単に触れます。
JIT コンパイラーは、Python、JavaScript、Java などの高水準言語で特に役立ちます。これらの言語は柔軟性と使いやすさで知られていますが、C や C++ などの低レベル言語に比べて実行速度が遅い場合があります。 JIT コンパイルは、実行時のコードの実行を最適化し、これらの高水準言語の利点を犠牲にすることなくコードの実行を高速化することで、このギャップを埋めるのに役立ちます。
Numba JIT コンパイラーで nopython=True モードを使用すると、Python インタープリターは完全にバイパスされ、Numba はすべてをマシンコードまでコンパイルする必要があります。これにより、Python の動的型付けやその他のインタープリター関連の操作に関連するオーバーヘッドが排除され、実行がさらに高速化されます。
指数平滑化は、過去の観測値に加重平均を適用することでデータを平滑化するために使用される手法です。指数平滑法の公式は次のとおりです:
ここで:
式は指数平滑法を適用します。ここで、
これを 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:
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 中国語 Web サイトの他の関連記事を参照してください。