今日共有した記事には多くのテキストは含まれていませんが、主にコードが含まれています。非常に有益でわかりやすく、主に Python のパフォーマンスを向上させるための 20 のヒントを共有し、遅い Python に別れを告げる方法を教えてくれます。オリジナル著者の Kaiyuan はフルスタック プログラマーであり、Python、Java、PHP、および C++ を使用します。
1. アルゴリズムの時間計算量を最適化する
アルゴリズムの時間計算量は、プログラムの実行効率に最も大きな影響を与えます。Python では、適切なデータ構造を選択することで時間計算量を最適化できます。特定の要素を見つけるための list や set など。要素の時間計算量はそれぞれ O(n) と O(1) です。シナリオが異なれば、最適化方法も異なります。一般的に、分割統治、分岐結合、貪欲プログラミング、動的プログラミングなどの考え方があります。
2. 冗長なデータを削減します
たとえば、大きな対称行列を保存するには、上三角または下三角を使用します。 0 要素が大部分を占める行列では、疎行列表現を使用します。
3. copyとdeepcopyを適切に使用する
dictやlistなどのデータ構造を持つオブジェクトの場合、直接代入は参照を使用します。場合によっては、オブジェクト全体をコピーする必要があります。この場合、コピー パッケージで copy と deepcopy を使用できます。これらの 2 つの関数の違いは、後者が再帰的にコピーすることです。効率も異なります: (次のプログラムは ipython で実行されます)
import copy a = range(100000) %timeit -n 10 copy.copy(a) # 运行10次 copy.copy(a) %timeit -n 10 copy.deepcopy(a) 10 loops, best of 3: 1.55 ms per loop 10 loops, best of 3: 151 ms per loop
timeit の後の -n は実行回数を示し、最後の 2 行は 2 つの timeit の出力に対応します。以下同様です。 。後者の方が一桁遅いことがわかります。
4. dict または set を使用して要素を検索します
Python dict と set は (C++11 標準ライブラリの unowned_map と同様) ハッシュ テーブルを使用して実装され、要素を検索する時間計算量は O(1) です。 。
a = range(1000) s = set(a) d = dict((i,1) for i in a) %timeit -n 10000 100 in d %timeit -n 10000 100 in s 10000 loops, best of 3: 43.5 ns per loop 10000 loops, best of 3: 49.6 ns per loop
dict の方がわずかに効率的です (そしてより多くのスペースを必要とします)。
5. ジェネレーターと yield の適切な使用
%timeit -n 100 a = (i for i in range(100000)) %timeit -n 100 b = [i for i in range(100000)] 100 loops, best of 3: 1.54 ms per loop 100 loops, best of 3: 4.56 ms per loop
() を使用すると、必要なメモリ容量はリストのサイズとは関係がないため、効率が高くなります。たとえば、特定のアプリケーションでは、set(i for i in range(100000)) の方が set([i for i in range(100000)]) より高速になります。
ただし、ループのトラバーサルが必要な状況の場合:
%timeit -n 10 for x in (i for i in range(100000)): pass %timeit -n 10 for x in [i for i in range(100000)]: pass 10 loops, best of 3: 6.51 ms per loop 10 loops, best of 3: 5.54 ms per loop
後者の方が効率的ですが、ループに中断がある場合、ジェネレーターを使用する利点は明らかです。 Yield はジェネレーターの作成にも使用されます:
def yield_func(ls): for i in ls: yield i+1 def not_yield_func(ls): return [i+1 for i in ls] ls = range(1000000) %timeit -n 10 for i in yield_func(ls):pass %timeit -n 10 for i in not_yield_func(ls):pass 10 loops, best of 3: 63.8 ms per loop 10 loops, best of 3: 62.9 ms per loop
メモリがそれほど大きくないリストの場合、リストを直接返すこともできますが、Yield の方が読みやすいです (個人的な好み)。
Python2.x には、xrange 関数、itertools パッケージなどのジェネレーター関数が組み込まれています。
6. ループを最適化する
たとえば、次の最適化は 2 倍速くなります。
7. 複数の判断を最適化します。式の順序and の場合は、最も少ない条件を満たすものが最初に配置され、or の場合は、最も多くの条件を満たすものが最初に配置されます。例:
a = range(10000) size_a = len(a) %timeit -n 1000 for i in a: k = len(a) %timeit -n 1000 for i in a: k = size_a 1000 loops, best of 3: 569 µs per loop 1000 loops, best of 3: 256 µs per loop
a = range(2000)
%timeit -n 100 [i for i in a if 10 < i < 20 or 1000 < i < 2000]
%timeit -n 100 [i for i in a if 1000 < i < 2000 or 100 < i < 20]
%timeit -n 100 [i for i in a if i % 2 == 0 and i > 1900]
%timeit -n 100 [i for i in a if i > 1900 and i % 2 == 0]
100 loops, best of 3: 287 µs per loop
100 loops, best of 3: 214 µs per loop
100 loops, best of 3: 128 µs per loop
100 loops, best of 3: 56.1 µs per loop
join は累積メソッドで約 5 倍の改善があります。
9. 適切な書式設定文字メソッドを選択しますIn [1]: %%timeit
...: s = ''
...: for i in a:
...: s += i
...:
10000 loops, best of 3: 59.8 µs per loop
In [2]: %%timeit
s = ''.join(a)
...:
100000 loops, best of 3: 11.8 µs per loop
3 つのケースの中で、% メソッドが最も遅いですが、3 つの間の差は大きくありません (すべて非常に高速です)。 (個人的には%が一番読みやすいと思います)
10.中間変数を使わずに2つの変数の値を交換しますs1, s2 = 'ax', 'bx'
%timeit -n 100000 'abc%s%s' % (s1, s2)
%timeit -n 100000 'abc{0}{1}'.format(s1, s2)
%timeit -n 100000 'abc' + s1 + s2
100000 loops, best of 3: 183 ns per loop
100000 loops, best of 3: 169 ns per loop
100000 loops, best of 3: 103 ns per loop
c=aの代わりにa,b=b,aを使用します。 a= b;b=c; a と b の値を交換すると、1 倍以上速くなります。
11. if is を使用するIn [3]: %%timeit -n 10000
a,b=1,2
....: c=a;a=b;b=c;
....:
10000 loops, best of 3: 172 ns per loop
In [4]: %%timeit -n 10000
a,b=1,2a,b=b,a
....:
10000 loops, best of 3: 86 ns per loop
if is True を使用すると、if == True よりもほぼ 2 倍高速になります。
12. カスケードを使用して x < y < zx < を比較すると、より効率的で読みやすくなります。
13. while 1 は while True よりも高速です
a = range(10000) %timeit -n 100 [i for i in a if i == True] %timeit -n 100 [i for i in a if i is True] 100 loops, best of 3: 531 µs per loop 100 loops, best of 3: 362 µs per loop
while 1 は、Python2.x ではキーワードではなくグローバル変数であるため、while true よりもはるかに高速です。
14. pow
x, y, z = 1,2,3 %timeit -n 1000000 if x < y < z:pass %timeit -n 1000000 if x < y and y < z:pass 1000000 loops, best of 3: 101 ns per loop 1000000 loops, best of 3: 121 ns per loop
**の代わりに**を使用すると、10倍以上高速になります。
15. cProfile、cStringIO、cPickleを使用して同じ関数
を実装します(それぞれprofile、StringIO、pickleに対応します)def while_1(): n = 100000 while 1: n -= 1 if n <= 0: break def while_true(): n = 100000 while True: n -= 1 if n <= 0: break m, n = 1000000, 1000000 %timeit -n 100 while_1() %timeit -n 100 while_true() 100 loops, best of 3: 3.69 ms per loop 100 loops, best of 3: 5.61 ms per loop
cで実装されたパッケージは10倍以上高速です!
16. 最適な逆シリアル化メソッドを使用します
以下は、対応する文字列を逆シリアル化するための eval、cPickle、および json メソッドの効率を比較しています:%timeit -n 10000 c = pow(2,20) %timeit -n 10000 c = 2**20 10000 loops, best of 3: 284 ns per loop 10000 loops, best of 3: 16.9 ns per loop
17. C 拡張機能 (Extension) を使用する
現在、CPython (Python の最も一般的な実装方法) ネイティブ API、ctypes、Cython、および cffi の 3 つのメソッドがあり、その機能は Python プログラムが C を呼び出せるようにすることです。コンパイルされたダイナミック リンク ライブラリの特徴は次のとおりです:CPython ネイティブ API: Python.h ヘッダー ファイルを導入することにより、Python データ構造を対応する C プログラムで直接使用できるようになります。実装プロセスは比較的面倒ですが、応用範囲は比較的広いです。
ctypes: 通常、C プログラムをラップするために使用され、純粋な Python プログラムがダイナミック リンク ライブラリ (Windows の dll または Unix のファイル) 内の関数を呼び出すことができるようにします。 Python で既存の C ライブラリを使用したい場合は、ctypes を使用するのが良い選択です。いくつかのベンチマーク テストによれば、python2+ctypes が最適な実行方法です。
Cython: Cython は、C 拡張機能の作成プロセスを簡素化する CPython のスーパーセットです。 Cython の利点は、その構文が簡潔であり、多数の C 拡張機能を含む numpy などのライブラリとの互換性が高いことです。 Cython の実現シナリオは通常、プロジェクト内の特定のアルゴリズムまたはプロセスの最適化を目的としています。一部のテストでは、パフォーマンスが何百倍も向上する場合があります。
cffi: cffi は pypy での ctypes の実装であり (詳細については以下を参照)、CPython とも互換性があります。 cffi は、Python で C クラス ライブラリを使用する方法を提供し、C コードを Python コードで直接記述し、既存の C クラス ライブラリへのリンクをサポートします。
これらの最適化手法の使用は、通常、既存のプロジェクトのパフォーマンスのボトルネック モジュールを最適化することを目的としており、元のプロジェクトに少し変更を加えるだけでプログラム全体の動作効率を大幅に向上させることができます。
18. 並列プログラミング
GIL の存在により、Python はマルチコア CPU を最大限に活用することが困難です。ただし、次の並列モードは、組み込みモジュールのマルチプロセッシングを通じて実現できます。
マルチプロセス: CPU を集中的に使用するプログラムの場合、マルチプロセッシングの Process、Pool、およびその他のカプセル化されたクラスを使用して、複数のプロセスを通じて実装できます。 . 並列コンピューティング。ただし、プロセス間の通信コストが比較的大きいため、プロセス間で大量のデータのやり取りを必要とするプログラムの効率はあまり向上しない場合があります。
マルチスレッド: IO集中型プログラムの場合、multiprocessing.dummyモジュールはマルチプロセッシングインターフェースを使用してスレッドをカプセル化し、マルチスレッドプログラミングを非常に簡単にします(たとえば、シンプルなプールマップインターフェースを使用できます)そして効率的です)。
分散: マルチプロセッシングの Managers クラスは、異なるプロセス間でデータを共有する方法を提供し、これに基づいて分散プログラムを開発できます。
さまざまなビジネス シナリオで、プログラムのパフォーマンスを最適化するために 1 つまたは複数の組み合わせを選択できます。
19. 究極のキラー: PyPy
PyPy は、RPython (CPython のサブセット) を使用して実装された Python であり、公式 Web サイトのベンチマーク テスト データによると、CPython で実装された Python よりも 6 倍以上高速です。高速な理由は、静的コンパイラー (gcc、javac など) とは異なり、実行中のプロセスからのデータを使用する、ジャストインタイム (JIT) コンパイラー、つまり動的コンパイラーを使用するためです。最適化のためのプログラム。歴史的な理由により、GIL は依然として pypy に保持されていますが、進行中の STM プロジェクトは GIL なしで PyPy を Python に変換しようとしています。
Python プログラムに C 拡張機能 (非 CFI メソッド) が含まれている場合、JIT の最適化効果は大幅に減少し、CPython よりも (Numpy よりも) 遅くなります。したがって、PyPy では、純粋な Python を使用するか、cffi 拡張機能を使用することをお勧めします。
STM、Numpy、その他のプロジェクトの改善により、PyPy が CPython に置き換わると信じています。
20. パフォーマンス分析ツールを使用する
上記の ipython で使用される timeit モジュールに加えて、cProfile もあります。 cProfile の使用方法も非常に簡単です: python -m cProfile filename.py filename.py は、実行されるプログラムのファイル名です。標準出力で各関数の呼び出し回数と実行時間を確認できます。プログラムのパフォーマンスのボトルネックを見つけて、対象を絞った方法で最適化できます。
以上がこの記事の全内容です。皆さんの学習に役立つことを願っています。また、皆さんも PHP 中国語 Web サイトをサポートしていただければ幸いです。
Python をうまく動かすための 20 のヒントと関連記事については、PHP 中国語 Web サイトに注目してください。