timeitとcProfileを使用してPythonコードのプロファイリングを行う
Pythonコードのプロファイリングを行うには、timeitとcProfileを使用する
ソフトウェア開発者として、おそらく「早期最適化はすべての悪の根源」という引用を何度も聞いたことがあるでしょう。最適化は小規模なプロジェクトにはあまり役立たないかもしれませんが、プロファイリングはしばしば役立ちます。
モジュールのコーディングが完了した後、コードの各セクションが実行される時間を測定するためにコードをプロファイリングすることは、コードの品質を改善するためのコードスメルの特定や最適化のガイドに役立ちます。ですので、最適化する前に常にコードをプロファイリングしましょう!
最初のステップを踏むために、このガイドは組み込みのtimeit
とcProfile
モジュールを使用してPythonでプロファイリングを開始するのに役立ちます。コマンドラインインターフェースとPythonスクリプト内の相当する呼び出し可能な関数の両方の使用方法を学びます。
timeitを使用してPythonコードをプロファイリングする方法
timeit
モジュールはPython標準ライブラリの一部であり、コードの短いスニペットの実行時間を計測するために使用できるいくつかの便利な関数を提供しています。
Pythonリストの逆転をするという単純な例を見てみましょう。以下の方法でリストの逆転コピーを取得することの実行時間を測定します:
reversed()
関数を使用する方法、および- リストスライシングを使用する方法。
>>> nums=[6,9,2,3,7]
>>> list(reversed(nums))
[7, 3, 2, 9, 6]
>>> nums[::-1]
[7, 3, 2, 9, 6]
コマンドラインでのtimeitの実行
コマンドラインでtimeit
を実行するには、次の構文を使用します:
$ python -m timeit -s 'setup-code' -n 'number' -r 'repeat' 'stmt'
実行時間を測定するステートメントstmt
を指定する必要があります。
必要に応じてsetup
コードを指定できます。-sオプションまたは–setupオプションを使用します。setupコードは一度だけ実行されます。
ステートメントを実行する回数を指定する必要はありません。-nオプションまたは–numberオプションを使用することができます。また、このサイクルを繰り返す回数を指定する必要もありません。-rオプションまたは–repeatオプションを使用します。
上記の例を実行してみましょう:
リストの作成はsetup
コードであり、リストの逆転は測定されるステートメントです:
$ python -m timeit -s 'nums=[6,9,2,3,7]' 'list(reversed(nums))'
500000 loops, best of 5: 695 nsec per loop
repeat
の値を指定しない場合、デフォルト値の5が使用されます。また、number
を指定しない場合、コードは0.2秒以上かかるように必要な回数だけ実行されます。
この例では、ステートメントを実行する回数を明示的に設定しています:
$ python -m timeit -s 'nums=[6,9,2,3,7]' -n 100Bu000 'list(reversed(nums))'
100000 loops, best of 5: 540 nsec per loop
repeat
のデフォルト値は5ですが、適切な値に設定することもできます:
$ python3 -m timeit -s 'nums=[6,9,2,3,7]' -r 3 'list(reversed(nums))'
500000 loops, best of 3: 663 nsec per loop
リストスライシングのアプローチにも時間を計測してみましょう:
$ python3 -m timeit -s 'nums=[6,9,2,3,7]' 'nums[::-1]'
1000000 loops, best of 5: 142 nsec per loop
リストスライスのアプローチは、より高速なようです(すべての例はPython 3.10を使用してUbuntu 22.04上で実行されています)。
Pythonスクリプト内でのtimeitの実行
次に、Pythonスクリプト内でtimeitを実行する方法を示します:
import timeit
setup = 'nums=[9,2,3,7,6]'
number = 100000
stmt1 = 'list(reversed(nums))'
stmt2 = 'nums[::-1]'
t1 = timeit.timeit(setup=setup,stmt=stmt1,number=number)
t2 = timeit.timeit(setup=setup,stmt=stmt2,number=number)
print(f"reversed()関数を使用:{t1}")
print(f"リストスライスを使用:{t2}")
timeit()
は、stmt
の実行時間をnumber
回返します。実行回数を明示的に指定することもできますし、デフォルト値の1000000を使用することもできます。
出力 >>
reversed()関数を使用:0.08982690000000002
リストスライスを使用:0.015550800000000004
このコードは、指定したnumber
回のステートメントを実行し、実行時間を返します。また、time.repeat()
を使用して最小時間を取得することも一般的です:
import timeit
setup = 'nums=[9,2,3,7,6]'
number = 100000
stmt1 = 'list(reversed(nums))'
stmt2 = 'nums[::-1]'
t1 = min(timeit.repeat(setup=setup,stmt=stmt1,number=number))
t2 = min(timeit.repeat(setup=setup,stmt=stmt2,number=number))
print(f"reversed()関数を使用:{t1}")
print(f"リストスライスを使用:{t2}")
このコードでは、指定したnumber
回のコードをrepeat
回繰り返し実行し、最小の実行時間を返します。ここでは、5回の繰り返しでそれぞれ100000回実行しています。
出力 >>
reversed()関数を使用:0.055375300000000016
リストスライスを使用:0.015101400000000043
cProfileを使用してPythonスクリプトのプロファイリングをする方法
短いコードスニペットの実行時間を測定するためにtimeitを使用する方法を見てきましたが、実際のところ、Pythonスクリプト全体のプロファイリングを行う方が役立ちます。
これにより、組み込みの関数やメソッドを含むすべての関数とメソッドの実行時間を取得できます。より高コストな関数呼び出しや最適化の機会を特定することができます。例えば、遅すぎるAPI呼び出しがあるかもしれません。または、関数によるループをよりPythonicな内包表現で置き換えることができるかもしれません。
Python標準ライブラリの一部であるcProfileモジュールを使用してPythonスクリプトのプロファイリング方法を学びましょう。
次のPythonスクリプトを考えてみましょう:
# main.py
import time
def func(num):
for i in range(num):
print(i)
def another_func(num):
time.sleep(num)
print(f"{num}秒間スリープしました")
def useful_func(nums, target):
if target in nums:
return nums.index(target)
if __name__ == "__main__":
func(1000)
another_func(20)
useful_func([2, 8, 12, 4], 12)
上記のスクリプトには次の3つの関数があります:
func()
は数の範囲をループし、それを出力します。another_func()
にはsleep()
関数の呼び出しが含まれています。useful_func()
はリスト内の目標の数値のインデックスを返します(目標がリストに存在する場合)。
上記の関数は、main.pyスクリプトを実行するたびに呼び出されます。
コマンドラインでのcProfileの実行
次のコマンドを使用してコマンドラインでcProfileを実行します:
python3 -m ファイル名.py
ここでは、ファイルをmain.pyと名付けました:
python3 -m main.py
これを実行すると、次の出力が表示されます:
出力 >>
0
...
999
20秒間スリープしました
そして、次のプロファイルが表示されます:
ここで、ncalls
は関数の呼び出し回数を示し、percall
は関数呼び出しごとの時間を示します。もしncalls
の値が1より大きい場合、percall
はすべての呼び出しの平均時間です。
スクリプトの実行時間は、ビルトインのsleep
関数呼び出しを使用するanother_func
によって支配されています(20秒間スリープします)。また、print
関数の呼び出しもかなり負荷がかかっていることがわかります。
PythonスクリプトでのcProfileの使用
コマンドラインでcProfileを実行することもできますが、Pythonスクリプトにプロファイリング機能を追加することもできます。プロファイリングと統計情報へのアクセスには、cProfileをpstatsモジュールと組み合わせて使用することができます。
リソースのセットアップと解放をより良く処理するためのベストプラクティスとして、with文を使用してコンテキストマネージャとして使用するプロファイルオブジェクトを作成します:
# main.py
import pstats
import time
import cProfile
def func(num):
for i in range(num):
print(i)
def another_func(num):
time.sleep(num)
print(f"{num}秒間スリープしました")
def useful_func(nums, target):
if target in nums:
return nums.index(target)
if __name__ == "__main__":
with cProfile.Profile() as profile:
func(1000)
another_func(20)
useful_func([2, 8, 12, 4], 12)
profile_result = pstats.Stats(profile)
profile_result.print_stats()
生成された出力プロファイルを詳しく見てみましょう:
大規模なスクリプトをプロファイリングする場合、実行時間に基づいて結果をソートすると便利です。プロファイルオブジェクト上でsort_stats
を呼び出して実行時間に基づいてソートすることができます:
...
if __name__ == "__main__":
with cProfile.Profile() as profile:
func(1000)
another_func(20)
useful_func([2, 8, 12, 4], 12)
profile_result = pstats.Stats(profile)
profile_result.sort_stats(pstats.SortKey.TIME)
profile_result.print_stats()
スクリプトを実行すると、時間に基づいてソートされた結果が表示されます:
結論
このガイドがPythonでのプロファイリングの初歩を理解するのに役立つことを願っています。常に視認性を損なわないように最適化することを忘れないでください。他のプロファイラ、サードパーティのPythonパッケージを含む他のプロファイラについて学びたい場合は、Pythonプロファイラに関するこの記事をチェックしてください。Bala Priya Cは、インドの開発者兼技術ライターです。彼女は数学、プログラミング、データサイエンス、コンテンツ作成の交差点で働くことが好きです。彼女の関心と専門知識の範囲には、DevOps、データサイエンス、自然言語処理が含まれます。彼女は読書、執筆、コーディング、コーヒーが好きです!現在、彼女はチュートリアル、ハウツーガイド、意見記事などを執筆して開発者コミュニティとの知識共有を学び、共有することに取り組んでいます。
We will continue to update VoAGI; if you have any questions or suggestions, please contact us!
Was this article helpful?
93 out of 132 found this helpful
Related articles