Pythonのパフォーマンス tips あなたが知っておくべきこと
Emily Parker
Product Engineer · Leapcell

##Pythonコードのパフォーマンス最適化のための包括的なガイド
Pythonは動的型付けのインタープリタ言語であるため、Cのような静的型付けのコンパイル言語と比較して実行速度が遅い場合があります。しかし、特定のテクニックと戦略を通じて、Pythonコードのパフォーマンスを大幅に向上させることができます。
この記事では、Pythonコードを最適化して、より速く、より効率的に実行する方法を探ります。Pythonのtimeit
モジュールを利用して、コードの実行時間を正確に測定します。
注記: デフォルトでは、timeit
モジュールは、測定結果の精度と安定性を確保するために、コードの実行を100万回繰り返します。
def print_hi(name): print(f'Hi, {name}') if __name__ == '__main__': # Execute the print_hi('leapcell') method t = timeit.Timer(setup='from __main__ import print_hi', stmt='print_hi("leapcell")') t.timeit()
##Pythonスクリプトの実行時間を計算する方法
time
モジュールでは、time.perf_counter()
が高精度のタイマーを提供し、これは短い時間間隔の測定に適しています。例えば:
import time # Record the start time of the program start_time = time.perf_counter() # Your code logic #... # Record the end time of the program end_time = time.perf_counter() # Calculate the running time of the program run_time = end_time - start_time print(f"Program running time: {run_time} seconds")
##I. I/O集中型操作 I/O集中型操作(Input/Output Intensive Operation)とは、実行時間のほとんどを入力/出力操作の完了を待つことに費やすプログラムまたはタスクを指します。I/O操作には、ディスクからのデータの読み取り、ディスクへのデータの書き込み、ネットワーク通信などが含まれます。これらの操作は通常、ハードウェアデバイスを伴うため、その実行速度はハードウェアの性能とI/O帯域幅によって制限されます。
それらの特性は次のとおりです。
- 待ち時間: プログラムがI/O操作を実行する場合、多くの場合、データが外部デバイスからメモリに、またはメモリから外部デバイスに転送されるのを待つ必要があり、これによりプログラムの実行がブロックされる可能性があります。
- CPU使用率: I/O操作の待ち時間により、CPUはこの期間中にアイドル状態になる可能性があり、CPU使用率が低下します。
- パフォーマンスのボトルネック: I/O操作の速度は、特にデータ量が多い場合や伝送速度が遅い場合に、プログラムのパフォーマンスのボトルネックになることがよくあります。
たとえば、I/O集中型操作print
を使用し、それを100万回実行します。
import time import timeit def print_hi(name): print(f'Hi, {name}') return if __name__ == '__main__': start_time = time.perf_counter() # Execute the print_hi('leapcell') method t = timeit.Timer(setup='from __main__ import print_hi', stmt='print_hi("leapcell")') t.timeit() end_time = time.perf_counter() run_time = end_time - start_time print(f"Program running time: {run_time} seconds")
実行結果は3秒です。
また、I/O操作を使用せずにメソッドを実行する場合、つまり、print()
を使用せずにprint_hi('xxxx')
の空のメソッドを呼び出すと、プログラムは大幅に高速になります。
def print_hi(name): # print(f'Hi, {name}') return
###I/O集中型操作の最適化方法 コードで必要な場合、たとえばファイルの読み取りと書き込みなど、次の方法を使用して効率を向上させることができます。
- 非同期I/O:
asyncio
などの非同期プログラミングモデルを使用します。これにより、プログラムはI/O操作の完了を待っている間、他のタスクの実行を継続できるため、CPU使用率が向上します。 - バッファリング: バッファを使用してデータを一時的に保存し、I/O操作の頻度を減らします。
- 並列処理: 複数のI/O操作を並行して実行して、全体的なデータ処理速度を向上させます。
- データ構造の最適化: 適切なデータ構造を選択して、データの読み取りおよび書き込みの回数を減らします。
##II. ジェネレーターを使用してリストと辞書を生成する Python 2.7以降のバージョンでは、リスト、辞書、およびセットジェネレーターに改善が導入され、データ構造の構築プロセスがより簡潔かつ効率的になっています。
###1. 従来の方法
def fun1(): list=[] for i in range(100): list.append(i) if __name__ == '__main__': start_time = time.perf_counter() t = timeit.Timer(setup='from __main__ import fun1', stmt='fun1()') t.timeit() end_time = time.perf_counter() run_time = end_time - start_time print(f"Program running time: {run_time} seconds") # Output result: Program running time: 3.363 seconds
###2. ジェネレーターでコードを最適化する
注:以下のコンテンツの便宜のため、main関数のコード部分main
は省略されています。
def fun1(): list=[ i for i in range(100)] # Program running time: 2.094 seconds
上記の導出式プログラムからわかるように、より簡潔で理解しやすいだけでなく、より高速です。これにより、この方法はリストとループを生成するための推奨される方法になります。
##III. 文字列の連結を避けて、join()
を使用する
join()
は、Pythonの文字列メソッドであり、シーケンス内の要素を文字列に連結(またはスプライス)するために使用されます。通常、特定の区切り文字が使用されます。その利点は通常次のとおりです。
- 高効率:
join()
は、文字列を連結するための効率的な方法であり、特に多数の文字列を処理する場合に効率的です。通常、+
演算子または%
フォーマットを使用するよりも高速です。多数の文字列を連結する場合、join()
メソッドは通常、1つずつ連結するよりも多くのメモリを節約します。 - 簡潔さ:
join()
を使用すると、コードがより簡潔になり、文字列の連結操作が繰り返されるのを回避できます。 - 柔軟性: 任意の文字列を区切り文字として指定できるため、文字列のスプライスに非常に柔軟性があります。
- 幅広いアプリケーション: 文字列だけでなく、リストやタプルなどのシーケンスタイプにも使用できます。ただし、要素を文字列に変換できる場合に限ります。
たとえば:
def fun1(): obj=['hello','this','is','leapcell','!'] s="" for i in obj: s+=i # Program running time: 0.35186 seconds
join()
を使用して文字列の連結を実現します。
def fun1(): obj=['hello','this','is','leapcell','!'] "".join(obj) # Program running time: 0.1822 seconds
join()
を使用すると、関数の実行時間が0.35秒から0.18秒に短縮されます。
##IV. ループの代わりにMap
を使用する
ほとんどの場合、従来のfor
ループは、より効率的なmap()
関数に置き換えることができます。map()
は、Pythonに組み込まれている高階関数であり、リスト、タプル、文字列などのさまざまな反復可能なデータ構造に指定された関数を適用できます。map()
を使用する主な利点は、明示的なループコードの記述を回避できる、より簡潔で効率的なデータ処理方法を提供することです。
###従来型ループ法
def fun1(): arr=["hello", "this", "is", "leapcell", "!"] new = [] for i in arr: new.append(i) # Program running time: 0.3067 seconds
###map()
関数を使用して同じ関数を実行する
def fun2(x): return x def fun1(): arr=["hello", "this", "is", "leapcell", "!"] map(fun2,arr) # Program running time: 0.1875 seconds
比較すると、map()
を使用すると、約半分の時間を節約でき、実行効率が大幅に向上します。
##V. 適切なデータ構造を選択する 適切なデータ構造を選択することは、Pythonコードの実行効率を向上させるために重要です。さまざまなデータ構造は、特定の操作に最適化されています。合理的な選択を行うと、データの検索、追加、削除が高速化され、プログラム全体の操作効率が向上します。
たとえば、コンテナ内の要素を判断する場合、辞書のルックアップ効率はリストよりも高くなりますが、これは大量のデータの場合です。少量のデータの場合は、逆になります。
###少量のデータでテストする
def fun1(): arr=["hello", "this", "is", "leapcell", "!"] 'hello' in arr 'my' in arr # Program running time: 0.1127 seconds def fun1(): arr={"hello", "this", "is", "leapcell", "!"} 'hello' in arr 'my' in arr # Program running time: 0.1702 seconds
###numpy
を使用して100個の整数をランダムに生成する
import numpy as np def fun1(): nums = {i for i in np.random.randint(100, size=100)} 1 in nums # Program running time: 14.28 seconds def fun1(): nums = {i for i in np.random.randint(100, size=100)} 1 in nums # Program running time: 13.53 seconds
少量のデータの場合、list
の実行効率はdict
よりも高くなりますが、大量のデータの場合、dict
の効率はlist
よりも高くなります。
頻繁な追加および削除操作があり、追加および削除される要素の数が多い場合、list
の効率は高くありません。このとき、collections.deque
を検討する必要があります。collections.deque
は両端キューであり、スタックとキューの両方の特性を備えており、両端で$O(1)$の複雑さで挿入および削除操作を実行できます。
###collections.deque
の使用法
from collections import deque def fun1(): arr=deque()# Create an empty deque for i in range(1000000): arr.append(i) # Program running time: 0.0558 seconds def fun1(): arr=[] for i in range(1000000): arr.append(i) # Program running time: 0.06077 seconds
list
のルックアップ操作も非常に時間がかかります。list
内の特定の要素を頻繁にルックアップしたり、これらの要素を順序付けられた方法でアクセスしたりする必要がある場合、bisect
を使用してlist
オブジェクトの順序を維持し、その中でバイナリ検索を実行してルックアップ効率を向上させることができます。
##VI. 不要な関数呼び出しを避ける Pythonプログラミングでは、コード効率を向上させるために、関数呼び出しの数を最適化することが重要です。過剰な関数呼び出しはオーバーヘッドを増やすだけでなく、追加のメモリを消費する可能性があり、プログラムの実行速度が低下します。パフォーマンスを向上させるには、不要な関数呼び出しを減らし、複数の操作を1つにまとめて、実行時間とリソース消費を削減するように努める必要があります。このような最適化戦略は、より効率的で高速なコードの作成に役立ちます。
##VII. 不要なimport
を避ける
Pythonのimport
ステートメントは比較的高速ですが、各import
には、モジュールの検索、モジュールコードの実行(まだ実行されていない場合)、およびモジュールオブジェクトを現在の名前空間に配置することが含まれます。これらの操作にはすべて、一定の時間とメモリが必要です。不要なモジュールをインポートすると、これらのオーバーヘッドが増加します。
##VIII. グローバル変数を使用しない
import math size=10000 def fun1(): for i in range(size): for j in range(size): z = math.sqrt(i) + math.sqrt(j) # Program running time: 15.6336 seconds
多くのプログラマーは、最初にPython言語でいくつかの簡単なスクリプトを記述します。スクリプトを記述する場合、通常、上記のコードのように、グローバル変数として直接記述することに慣れています。ただし、グローバル変数とローカル変数の実装方法が異なるため、グローバルスコープで定義されたコードは、関数で定義されたコードよりもはるかに遅く実行されます。スクリプトステートメントを関数に入れることで、通常15%〜30%の速度向上が達成できます。
import math def fun1(): size = 10000 for i in range(size): for j in range(size): z = math.sqrt(i) + math.sqrt(j) # Program running time: 14.9319 seconds
##IX. モジュールおよび関数属性へのアクセスを避ける
import math # 推奨されません def fun2(size: int): result = [] for i in range(size): result.append(math.sqrt(i)) return result def fun1(): size = 10000 for _ in range(size): result = fun2(size) # Program running time: 10.1597 seconds
.
(属性アクセス演算子)を使用するたびに、__getattribute__()
や__getattr__()
などの特定のメソッドがトリガーされます。これらのメソッドは辞書操作を実行するため、追加の時間オーバーヘッドが発生します。from import
ステートメントを使用すると、属性アクセスを排除できます。
from math import sqrt # 推奨:必要なモジュールのみをインポートします def fun2(size: int): result = [] for i in range(size): result.append(sqrt(i)) return result def fun1(): size = 10000 for _ in range(size): result = fun2(size) # Program running time: 8.9682 seconds
##X. 内側のfor
ループでの計算を減らす
import math def fun1(): size = 10000 sqrt = math.sqrt for x in range(size): for y in range(size): z = sqrt(x) + sqrt(y) # Program running time: 14.2634 seconds
上記のコードでは、sqrt(x)
は内側のfor
ループにあり、ループが実行されるたびに再計算され、不要な時間オーバーヘッドが追加されます。
import math def fun1(): size = 10000 sqrt = math.sqrt for x in range(size): sqrt_x=sqrt(x) for y in range(size): z = sqrt_x + sqrt(y) # Program running time: 8.4077 seconds
#Leapcell:Pythonアプリのホスティングに最適なサーバーレスプラットフォーム
最後に、Pythonアプリケーションをデプロイするための最適なプラットフォームをご紹介します。Leapcell
###1. 複数言語のサポート
- JavaScript、Python、Go、またはRustで開発します。
###2. 無制限のプロジェクトを無料でデプロイ
- 使用量に応じてのみ支払います。リクエストも料金も発生しません。
###3. 無敵のコスト効率
- アイドル料金なしの従量課金制。
- 例:$25で、平均応答時間60ミリ秒で694万件のリクエストをサポートします。
###4. 合理化された開発者エクスペリエンス
- 簡単なセットアップのための直感的なUI。
- 完全に自動化されたCI/CDパイプラインとGitOps統合。
- 実用的な洞察のためのリアルタイムメトリックとロギング。
###5. 簡単なスケーラビリティと高いパフォーマンス
- 高い同時実行性を容易に処理するための自動スケーリング。
- 運用上のオーバーヘッドはゼロ。構築に集中するだけです。
Leapcell Twitter: https://x.com/LeapcellHQ