timeitとcProfileを使用してPythonコードのプロファイリングを行う

Pythonコードのプロファイリングを行うには、timeitとcProfileを使用する

 

ソフトウェア開発者として、おそらく「早期最適化はすべての悪の根源」という引用を何度も聞いたことがあるでしょう。最適化は小規模なプロジェクトにはあまり役立たないかもしれませんが、プロファイリングはしばしば役立ちます。

モジュールのコーディングが完了した後、コードの各セクションが実行される時間を測定するためにコードをプロファイリングすることは、コードの品質を改善するためのコードスメルの特定や最適化のガイドに役立ちます。ですので、最適化する前に常にコードをプロファイリングしましょう!

最初のステップを踏むために、このガイドは組み込みのtimeitcProfileモジュールを使用して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!

Share:

Was this article helpful?

93 out of 132 found this helpful

Discover more

人工知能

「Ntropyの共同創設者兼CEO、ナレ・ヴァルダニアンについて - インタビューシリーズ」

「Ntropyの共同創設者兼CEOであるナレ・ヴァルダニアンは、超人的な精度で100ミリ秒以下で金融取引を解析することを可能にす...

人工知能

Aaron Lee、Smith.aiの共同設立者兼CEO - インタビューシリーズ

アーロン・リーさんは、Smith.aiの共同創業者兼CEOであり、AIと人間の知性を組み合わせて、24時間365日の顧客エンゲージメン...

人工知能

「ジャスティン・マクギル、Content at Scaleの創設者兼CEO - インタビューシリーズ」

ジャスティンは2008年以来、起業家、イノベーター、マーケターとして活動しています彼は15年以上にわたりSEOマーケティングを...

人工知能

アーティスの創設者兼CEO、ウィリアム・ウーによるインタビューシリーズ

ウィリアム・ウーは、Artisseの創設者兼CEOであり、ユーザーの好みに基づいて写真を精密に変更する技術を提供していますそれ...

人工知能

スコット・スティーブンソン、スペルブックの共同創設者兼CEO- インタビューシリーズ

スコット・スティーブンソンは、Spellbookの共同創設者兼CEOであり、OpenAIのGPT-4および他の大規模な言語モデル(LLM)に基...

人工知能

「クリス・サレンス氏、CentralReachのCEO - インタビューシリーズ」

クリス・サレンズはCentralReachの最高経営責任者であり、同社を率いて、自閉症や関連する障害を持つ人々のために優れたクラ...