効率的な Python コード

巴扎黑
巴扎黑オリジナル
2017-04-05 15:45:551493ブラウズ

私の意見では、Python コミュニティは 3 つの流派、つまり Python 2.x 組織、3.x 組織、および PyPy 組織に分かれています。この分類は基本的に、ライブラリの互換性と速度に帰着します。この記事では、いくつかの一般的なコード最適化手法と、C にコンパイルした後の大幅なパフォーマンスの向上に焦点を当てます。もちろん、3 つの主要な Python ジャンルの実行時間も示します。私の目的は、一方が他方よりも優れていることを証明することではなく、単にこれらの特定の例をさまざまなコンテキストで使用して比較する方法のアイデアを提供することです。

発電機を使う

見落とされがちなメモリ最適化は、ジェネレーターの使用です。ジェネレーターを使用すると、一度にすべてのレコードを返すのではなく、一度に 1 つのレコードのみを返す関数を作成できます。これが、範囲の代わりに xrange を使用したり、フィルターの代わりに ifilter を使用したりする理由です。良い例としては、大規模なリストを作成し、それらをつなぎ合わせることが挙げられます。

りー

これにより、少し高速になるだけでなく、リスト全体をメモリに保存する必要がなくなります

Ctypesの紹介

主要なパフォーマンス コードについては、Python 自体も、主に ctypes を通じて C メソッドを呼び出すための API を提供します。C コードを記述せずに ctypes を使用できます。デフォルトでは、Python はプリコンパイルされた標準 C ライブラリを提供します。ジェネレーターの例に戻って、ctypes を使用して実装するのにどれくらいの時間がかかるかを見てみましょう。

りー

これをランダム関数 c に置き換えるだけで、実行時間が半分以下に短縮されました。今、もっと良くできると言ったら信じますか?

Cythonの紹介

Cython は Python のスーパーセットで、C 関数を呼び出したり、変数を宣言してパフォーマンスを向上させることができます。 Cython を使用する前にインストールする必要があります。

import timeit
import random

def generate(num):
while num:
yield random.randrange(10)
num -= 1

def create_list(num):
numbers = []
while num:
numbers.append(random.randrange(10))
num -= 1
return numbers
print(timeit.timeit("sum(generate(999))", setup="from __main__ import generate", number=1000))
>>> 0.88098192215 #Python 2.7
>>> 1.416813850402832 #Python 3.2
print(timeit.timeit("sum(create_list(999))", setup="from __main__ import create_list", number=1000))
>>> 0.924163103104 #Python 2.7
>>> 1.5026731491088867 #Python 3.2

Cython は基本的に、現在は開発されていない別の Pyrex のようなライブラリのフォークであり、Python のようなコードを Python ファイルで呼び出すことができる C ライブラリにコンパイルします。 Python ファイルには .py 接尾辞の代わりに .pyx 接尾辞を使用してください。 Cython を使用してジェネレータ コードを実行する方法を見てみましょう。

りー

Cython に関数をコンパイルできるように、setup.py を作成する必要があります。

りー

使用してコンパイルされました:

import timeit
from ctypes import cdll

def generate_c(num):
#Load standard C library
libc = cdll.LoadLibrary("libc.so.6") #Linux
#libc = cdll.msvcrt #Windows
while num:
yield libc.rand() % 10
num -= 1

print(timeit.timeit("sum(generate_c(999))", setup="from __main__ import generate_c", number=1000))
>>> 0.434374809265 #Python 2.7
>>> 0.7084300518035889 #Python 3.2

cython_generator.c ファイルとgenerator.so ファイルの 2 つのファイルが表示されるはずです。次の方法を使用してプログラムをテストします:

sudo pip install cython

悪くはありません。何か改善できる点がないか見てみましょう。最初に「num」を整数として宣言し、次にランダム関数を担当する標準 C ライブラリをインポートできます。

りー

再度コンパイルして実行すると、この驚くべき数値が表示されるでしょう。

りー

ほんの少しの変更だけでまともな結果が得られました。ただし、この変更は面倒な場合もあるので、通常の Python を使用して変更する方法を見てみましょう。

PyPy の紹介 PyPy は、Python 2.7.3 のジャストインタイム コンパイラーであり、平たく言えば、コードの実行を高速化することを意味します。 Quora は本番環境で PyPy を使用します。 PyPy のダウンロード ページにはインストール手順が記載されていますが、Ubuntu を使用している場合は、apt-get 経由でインストールできます。動作方法はすぐに使えるため、狂った bash やスクリプトの実行は不要で、ダウンロードして実行するだけです。元のジェネレーター コードが PyPy でどのように動作するかを見てみましょう。

りー

おお!コードを 1 行も変更しなくても、実行速度は純粋な Python 実装よりも 8 倍速くなります。

さらなるテストなぜさらなる研究をするのか?ピピがチャンピオンです!完全に真実ではありません。ほとんどのプログラムは PyPy 上で実行できますが、一部のライブラリは完全にはサポートされていません。さらに、コンパイラを変更するよりも、プロジェクト用の C 拡張機能を作成する方が簡単です。もう少し深く掘り下げて、ctypes を使用してどのように C でライブラリを作成できるかを見てみましょう。マージソートとフィボナッチ数列の計算の速度をテストしてみましょう。ここで使用する C コード (functions.c) を示します:

#cython_generator.pyx
import random

def generate(num):
while num:
yield random.randrange(10)
num -= 1

Linux プラットフォームでは、次の方法を使用して共有ライブラリにコンパイルできます:

from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext

setup(
cmdclass = {'build_ext': build_ext},
ext_modules = [Extension("generator", ["cython_generator.pyx"])]
)

ctypes を使用すると、以前に標準 C ライブラリで行ったのと同じように、「libfunctions.so」共有ライブラリをロードすることでこのライブラリを使用できます。ここでは、Python 実装と C 実装を比較します。ここで、フィボナッチ数列の計算を開始します:

python setup.py build_ext --inplace

予想通り、C は Python や PyPy よりも高速です。同じ方法でマージソートを比較することもできます。

Cypes ライブラリについてはまだ詳しく調べていないため、これらの例は Python の強力な側面を反映していません。Cypes ライブラリには、int、char 配列、float、bytes などの標準的な型の制限がいくつかあるだけです。デフォルトでは整数配列はありませんが、c_int (ctype は int 型) を乗算することで間接的にそのような配列を取得できます。これはコードの 7 行目でも示されています。数値の配列である c_int 配列を作成し、それらを c_int 配列にパックしました

  主要的是c语言不能这样做,而且你也不想。我们用指针来修改函数体。为了通过我们的c_numbers的数列,我们必须通过引用传递merge_sort功能。运行merge_sort后,我们利用c_numbers数组进行排序,我已经把下面的代码加到我的functions.py文件中了。

#Python Merge Sort
from random import shuffle, sample

#Generate 9999 random numbers between 0 and 100000
numbers = sample(range(100000), 9999)
shuffle(numbers)
c_numbers = (c_int * len(numbers))(*numbers)

from heapq import merge
def merge_sort(m):
if len(m) <= 1:
return m
middle = len(m) // 2
left = m[:middle]
right = m[middle:]
left = merge_sort(left)
right = merge_sort(right)
return list(merge(left, right))

start = time.time()
numbers = merge_sort(numbers)
finish = time.time()
print("Python: " + str(finish - start))

#C Merge Sort
start = time.time()
libfunctions.merge_sort(byref(c_numbers), len(numbers))
finish = time.time()
print("C: " + str(finish - start))
Python: 0.190635919571 #Python 2.7
Python: 0.11785483360290527 #Python 3.2
Python: 0.266992092133 #PyPy 1.9
Python: 0.265724897385 #PyPy 2.0b1
C: 0.00201296806335 #Python 2.7 + ctypes
C: 0.0019741058349609375 #Python 3.2 + ctypes
C: 0.0029308795929 #PyPy 1.9 + ctypes
C: 0.00287103652954 #PyPy 2.0b1 + ctypes

  这儿通过表格和图标来比较不同的结果。

効率的な Python コード

  Merge Sort Fibonacci
Python 2.7 0.191 1.187
Python 2.7 + ctypes 0.002 0.044
Python 3.2 0.118 1.272
Python 3.2 + ctypes 0.002 0.046
PyPy 1.9 0.267 0.564
PyPy 1.9 + ctypes 0.003 0.048
PyPy 2.0b1 0.266 0.567
PyPy 2.0b1 + ctypes 0.003 0.046

以上が効率的な Python コードの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。