Popular Posts

コードの速度を低下させる Python の 7 つの間違い (および重要な修正)

コードの速度を低下させる Python の 7 つの間違い (および重要な修正)


Python が素晴らしいのは、学習が最も簡単な言語の 1 つであり、プロトタイプを驚くほど速く作成できるためです。ただし、その利便性のおかげで、作業が大幅に遅くなる可能性がある重大な非効率性が隠れていることがよくあります。注意しないと、作成したきちんとしたスクリプトがすぐに大きなパフォーマンスのボトルネックになる可能性があります。

基本的なスクリプト作成を超えて作業を進める場合は、よくある落とし穴について理解しておく必要があります。間違いを犯す前に何をしてはいけないかを知っておくと、時間を節約し、頭を悩ませることができます。

メンバーシップ検索にリストを使用する

コードの速度を低下させる Python の 7 つの間違い (および重要な修正) クレジット: Jorge Aguilar / How-To Geek

値がリスト内にあるかどうかを確認するとき ( の使用など)。 if item in my_list)、スクリプトは要素を順番にスキャンします。つまり、各要素をチェックして、ターゲットが見つからないか、最後にあるかどうかを確認する必要がある可能性があります。これは線形探索と呼ばれるものであり、 O(n) 手術。データが増大し始めると、要素の検索にかかる時間がリストのサイズに直接比例して増加するため、このプロセスは大幅に遅くなります。

言い換えれば、100 万項目のリストで特定の値を探している場合、Python は項目が存在しないと判断する前に、ターゲット値を 1000 万項目すべてと比較する必要があるかもしれません。メンバーシップ テストが別のループ内に配置されると、通常は 2 つのデータセットを比較するときに非常に遅くなります。

「n+1」クエリの問題

「n+1」クエリの問題の例 クレジット: Jorge Aguilar / How-To Geek

N+1 クエリの問題は、特にオブジェクト リレーショナル マッパーを使用している場合や外部 API を呼び出している場合に、アプリ開発で遭遇する最も一般的なパフォーマンスの問題と考えられます。これは、アプリケーションが最初のクエリを実行して項目のリスト (「1」) を取得し、その後、そのリスト内の各項目に対して個別のデータベース クエリまたは API 呼び出しをトリガーして、関連するファイルまたはコンテンツ (「N」) を取得するときに発生します。

コード内のロジックは問題ないように見えますが、項目をループして処理しているだけです。年齢が上がるにつれて、パフォーマンスの問題は悪化します。これにより、一瞬の操作が遅いクロールに変わり、実際にサーバー スレッドがフリーズする可能性があります。通常、ボトルネックは、個々のクエリの実行速度ではなく、データベースへの繰り返しのアクセスすべてにかかる総コストです。

ループ内で文字列を連結する

ループ内で文字列を連結する例 クレジット: Jorge Aguilar / How-To Geek

文字列は不変オブジェクトであるため、文字列を作成すると、その場で実際に変更することはできません。次のような文字列を指定してコマンドを実行すると、 += " data" ループ内で繰り返し、システムは古いコンテンツと新しいコンテンツの両方に十分なメモリの新しいチャンクを割り当てる必要があります。その後、元の文字列を新しい場所にコピーし、新しい部分を追加して、最後にクリーンアップを待っている古いオブジェクトを破棄します。

これを何千回も行うと、大量のメモリ チャーンが発生し、プロセッサに不必要で遅い作業を強いることになりますが、データ セットが大きい場合にはまったく役に立ちません。

ループ内で文字列を直接連結することは避ける必要があります。代わりに、リストと .join() メソッドを使用します。

ファイル全体をメモリに読み込む

ファイル全体をメモリに読み取る例 クレジット: Jorge Aguilar / How-To Geek

テキスト ファイルを読み取ることは Python ではハックとみなされることがありますが、単一のコマンドを使用してファイル全体をメモリに読み取ろうとしないでください。のようなメソッド f.read() または f.readlines() これらは小さなテキスト ファイルには便利ですが、データが大きくなり始めると完全に無駄になります。一度に数ギガバイトのデータをロードしようとすると、瞬間的に MemoryError がクラッシュする主な原因になります。

確実な解決策は、ファイルの丸呑みをやめてストリーミングを利用することです。 Python ファイル オブジェクトはイテレータであるため、それらを直接ループできます。 1つを使用する場合 for line in file_handler: ブロック内では、インタプリタは一度に 1 つのエントリを読み取って処理し、次の行に進む前にガベージ コレクタにそのメモリをすぐに解放させます。

非効率的なネストされたループ

非効率なネストされたループの例 クレジット: Jorge Aguilar / How-To Geek

非効率なネストされたループは、目立たないところに隠れることを好み、多くの場合、無害なリスト内包または標準の for ループのように見えるものの中に隠されます。コレクションをループし、そのコレクション内の項目ごとに、作成した別のデータ セットを反復処理する場合 O(n) 複雑。データ量が増加すると、計算コストは​​二次関数的に増加します。

この問題を解決する最も一般的かつ効果的な方法は、再パーティション化を開始する前にデータを再編成することです。その内部リストを検索辞書 (ハッシュ マップ) またはセットに変換します。 HashMap を使用すると、インタープリターはキーのハッシュ コードを計算し、それを使用してその値が存在する特定のメモリ バケットに直接移動し、他の要素をスキャンする必要性を完全に回避します。

リソースの頻繁な開閉

リソースのオープンとクローズを繰り返す例 クレジット: Jorge Aguilar / How-To Geek

Python には、ループ内でファイル ハンドルやデータベース接続などのリソースを継続的に開閉する機能がありません。くっつきやすいです open() Python のクリーンな構文により、呼び出しや接続リクエストはループ内で実行されますが、これを行うと、単一のパスごとにオペレーティング システムが高価なハンドシェイク プロセスを実行することになります。これにより、冗長なパフォーマンスが排除されます。

ここでの簡単な解決策は、リソース取得ロジック全体を反復ブロックから完全に取り出すことです。で行ったように、コンテキスト マネージャーを使用します。 open(...) ステートメントをループの開始前に配置します。リソースを 1 回開くと、必要なハンドシェイクが 1 回だけ確立されます。その後、すでに開いているハンドルを使用して、必要なすべての読み取りまたは書き込み操作のループに入ることができるため、CPU と入出力のオーバーヘッドが軽減されます。

組み込みの最適化された関数を無視する

最適化された組み込み関数を無視する例 クレジット: Jorge Aguilar / How-To Geek

リストを並べ替えたり、合計を計算したり、一部のデータをフィルターしたりするための独自のカスタム ロジックを作成したくなります。おそらくこれにより、より詳細な制御が可能になると思われるでしょう。ただし、これらの手動ループに依存することが、Python アプリの実行が遅い主な原因です。ループを効率的なマシンコードに変換するコンパイル言語とは異なり、Python の解釈される性質により、各反復にオーバーヘッドが追加されます。これは、生の for ループを実行するたびに、インタープリターの大きなオーバーヘッドが発生することを意味します。純粋な Python でループを作成する場合、インタープリターは常に命令をデコードし、型をチェックし、コレクション内の各項目の関数呼び出しを処理する必要があります。

標準ライブラリを信頼して使用してください。 Python の方法でコードを書き始めると、はるかに効率よくコードを作成できるようになります。


理解しておくべき重要なことは、コードの最適化はある種のアルゴリズムの魔法ではないということです。必要なのは、言語が実際にどのように機能するかを尊重し、理解することだけです。遅いスクリプトは通常、テストされていない小さな決定の積み重ねであり、結果としてパフォーマンスに大きな問題が生じます。

前進するには、コードを常にレビューしてクリーンアップすることが必要です。とにかくプログラミングは十分に挑戦的です。これらの一般的な落とし穴がどこにあるのかを学ぶことで、独自のワークフローに不必要なボトルネックを追加することがなくなります。

Leave a Reply

Your email address will not be published. Required fields are marked *