Cythonチュートリアル:Pythonを高速化する方法

Pythonは、習得と操作が簡単な強力なプログラミング言語ですが、特に数学や統計を扱う場合は、常に最速で実行できるとは限りません。CライブラリをラップするNumPyのようなサードパーティライブラリは、一部の操作のパフォーマンスを大幅に向上させることができますが、Pythonで直接Cの生の速度とパワーが必要な場合もあります。

Cythonは、Python用のC拡張機能の記述を容易にし、既存のPythonコードをCに変換できるようにするために開発されました。さらに、Cythonを使用すると、最適化されたコードを外部依存関係なしでPythonアプリケーションに同梱できます。

このチュートリアルでは、既存のPythonコードをCythonに変換し、それを本番アプリケーションで使用するために必要な手順を説明します。

関連ビデオ:Cythonを使用してPythonを高速化する

Cythonの例

Cythonのドキュメントから抜粋した簡単な例から始めましょう。これは、積分関数のあまり効率的ではない実装です。

def f(x):

    x ** 2-xを返す

defintegrate_f(a、b、N):

    s = 0

    dx =(ba)/ N

    範囲(N)のiの場合:

        s + = f(a + i * dx)

    s * dxを返す

コードは読みやすく理解しやすいですが、実行速度が遅くなります。これは、Pythonが自身のオブジェクトタイプとマシンの生の数値タイプの間で絶えず変換する必要があるためです。

ここで、同じコードのCythonバージョンを検討します。Cythonの追加は強調されています。

 cdef f(double x):

    x ** 2-xを返す

def integer_f(double a、double b、int N):

    cdef int i

    cdef double s、x、dx

    s = 0

    dx =(ba)/ N

    範囲(N)のiの場合:

        s + = f(a + i * dx)

    s * dxを返す

これらの追加により、コード全体で変数タイプを明示的に宣言できるため、Cythonコンパイラーはこれらの「装飾された」追加をCに変換できます。 

関連ビデオ:Pythonがプログラミングを容易にする方法

PythonはITに最適で、システムの自動化から機械学習などの最先端の分野での作業まで、さまざまな種類の作業を簡素化します。

Cython構文

Cythonコードを装飾するために使用されるキーワードは、従来のPython構文にはありません。それらはCython専用に開発されたため、それらで装飾されたコードは従来のPythonプログラムとして実行されません。

Cythonの構文の最も一般的な要素は次のとおりです。

変数タイプ

Cythonで使用される変数の型のいくつかは、以下のようなPythonの独自のタイプのエコー、ある intfloatlong。他のCython変数タイプも、charまたはのようなCにあり、のようなstruct宣言も同様unsigned longです。またbint、PythonTrue/False値の経営幹部レベルの表現など、Cythonに固有のものもあります。

関数型cdefcpdef

cdefキーワードはCythonまたはC型の使用を示しています。また、Pythonの場合と同じように関数を定義するためにも使用されます。

Pythonのdefキーワードを使用してCythonで記述された関数は、他のPythonコードからは見えますが、パフォーマンスが低下します。cdefキーワードを使用する関数は、他のCythonまたはCコードにのみ表示されますが、実行速度ははるかに速くなります。Cythonモジュール内から内部的にのみ呼び出される関数がある場合は、を使用しますcdef

3番目のキーワードであるcpdef、は、PythonコードとCコードの両方との互換性を提供し、Cコードが宣言された関数にフルスピードでアクセスできるようにします。ただし、この便利さには代償が伴います。cpdef関数は、よりも多くのコードを生成し、 呼び出しのオーバーヘッドがわずかに多くなりますcdef

その他のCythonキーワード

Cythonの他のキーワードは、Pythonでは利用できないプログラムフローと動作の側面を制御します。

  • gilおよびnogilこれらは、Pythonのグローバルインタープリターロック(GIL with gil:)を必要とする()または必要としない()コードのセクションを描くために使用されるコンテキストマネージャーwith nogil:です。Python APIを呼び出さないCコードはnogil、特にネットワーク接続からの読み取りなどの長時間実行操作を実行している場合、ブロック内でより高速に実行できます。
  • cimport。 これにより、Cythonは、Cデータ型、関数、変数、および拡張型をインポートするように指示されます。たとえば、NumPyのネイティブCモジュールを使用するCythonアプリは、cimportこれらの機能にアクセスするために使用します。
  • includeこれにより、Cとほぼ同じ方法で、あるCythonファイルのソースコードが別のファイル内に配置されます。Cythonには、includes以外のCythonファイル間で宣言を共有するためのより洗練された方法があることに注意してください。
  • ctypedef外部Cヘッダーファイルの型定義を参照するために使用されます。
  • externcdef他のモジュールにあるC関数または変数を参照するために使用されます。
  • public/api他のCコードで表示されるCythonモジュールでの宣言に使用されます。
  • inline速度を上げるために、特定の関数をインライン化するか、使用するたびにそのコードを呼び出し元の関数の本体に配置する必要があることを示すために使用されます。たとえばf、上記のコード例のinline関数は、1つの場所でしか使用されないため、関数呼び出しのオーバーヘッドを減らすために装飾することができます。(Cコンパイラは独自のインライン化を自動的に実行inlineする場合がありますが、何かをインライン化する必要があるかどうかを明示的に指定できることに注意してください。)

It is not necessary to know all of the Cython keywords in advance. Cython code tends to be written incrementally—first you write valid Python code, then you add Cython decoration to speed it up. Thus you can pick up Cython’s extended keyword syntax piecemeal, as you need it.

Compile Cython

Now that we have some idea of what a simple Cython program looks like and why it looks the way it does, let’s walk through the steps needed to compile Cython into a working binary.

To build a working Cython program, we will need three things:

  1. The Python interpreter. Use the most recent release version, if you can.
  2. The Cython package. You can add Cython to Python by way of the pip package manager: pip install cython
  3. A C compiler.

Item #3 can be tricky if you’re using Microsoft Windows as your development platform. Unlike Linux, Windows doesn’t come with a C compiler as a standard component. To address this, grab a copy of Microsoft Visual Studio Community Edition, which includes Microsoft’s C compiler and costs nothing. 

Note that, as of this writing, the most recent release version of Cython is 0.29.16, but a beta version of Cython 3.0 is available for use. If you use pip install cython, the most current non-beta version will be installed. If you want to try out the beta, use pip install cython>=3.0a1 to install the most recent edition of the Cython 3.0 branch. Cython’s developers recommend trying the Cython 3.0 branch whenever possible, because in some cases it generates significantly faster code.

Cython programs use the .pyx file extension. In a new directory, create a file named num.pyx that contains the Cython code example shown above (the second code sample under “A Cython example”) and a file named main.py that contains the following code:

from num import integrate_f

print (integrate_f(1.0, 10.0, 2000))

This is a regular Python progam that will call the integrate_f function found in num.pyx. Python code “sees” Cython code as just another module, so you don’t need to do anything special other than import the compiled module and run its functions.

Finally, add a file named setup.py with the following code:

from distutils.core import setup from distutils.extension import Extension from Cython.Build import cythonize ext_modules = [ Extension( r'num', [r'num.pyx'] ), ] setup( name="num", ext_modules=cythonize(ext_modules),

)

setup.py is normally used by Python to install the module it’s associated with, and can also be used to direct Python to compile C extensions for that module. Here we’re using setup.py to compile Cython code.

If you’re on Linux, and you have a C compiler installed (typically the case), you can compile the .pyx file to C by running the command: 

python setup.py build_ext --inplace

If you’re using Microsoft Windows and Microsoft Visual Studio 2017 or better, you’ll need to make sure you have the most recent version of setuptools installed in Python (version 46.1.3 as of this writing) before that command will work. This ensures that Python’s build tools will be able to auto-detect and use the version of Visual Studio you have installed.

If the compilation is successful, you should see new files appear in the directory: num.c (the C file generated by Cython) and a file with either a .o extension (on Linux) or a .pyd extension (on Windows). That’s the binary that the C file has been compiled into. You may also see a \build subdirectory, which contains the artifacts from the build process.

Run python main.py, and you should see something like the following returned as a response:

283.297530375

That’s the output from the compiled integral function, as invoked by our pure Python code. Try playing with the parameters passed to the function in main.py to see how the output changes.

Note that whenever you make changes to the .pyx file, you will need to recompile it. (Any changes you make to conventional Python code will take effect immediately.)

The resulting compiled file has no dependencies except the version of Python it was compiled for, and so can be bundled into a binary wheel. Note that if you refer to other libraries in your code, like NumPy (see below), you will need to provide those as part of the application’s requirements.

How to use Cython

Now that you know how to “Cythonize” a piece of code, the next step is to determine how your Python application can benefit from Cython. Where exactly should you apply it?

For best results, use Cython to optimize these kinds of Python functions:

  1. Functions that run in tight loops, or require long amounts of processing time in a single “hot spot” of code.
  2. Functions that perform numerical manipulations.
  3. Functions that work with objects that can be represented in pure C, such as basic numerical types, arrays, or structures, rather than Python object types like lists, dictionaries, or tuples.

Python has traditionally been less efficient at loops and numerical manipulations than other, non-interpreted languages. The more you decorate your code to indicate it should use base numerical types that can be turned into C, the faster it will do number-crunching.

Using Python object types in Cython isn’t itself a problem. Cython functions that use Python objects will still compile, and Python objects may be preferable when performance isn’t the top consideration. But any code that makes use of Python objects will be limited by the performance of the Python runtime, as Cython will generate code to directly address Python’s APIs and ABIs.

Another worthy target of Cython optimization is Python code that interacts directly with a C library. You can skip the Python “wrapper” code and interface with the libraries directly.

However, Cython does not automatically generate the proper call interfaces for those libraries. You will need to have Cython refer to the function signatures in the library’s header files, by way of a cdef extern from declaration. Note that if you don’t have the header files, Cython is forgiving enough to let you declare external function signatures that approximate the original headers. But use the originals whenever possible to be safe.

One external C library that Cython can use right out of the box is NumPy. To take advantage of Cython’s fast access to NumPy arrays, use cimport numpy (optionally with as np to keep its namespace distinct), and then use cdef statements to declare NumPy variables, such as cdef np.array or np.ndarray.

Cython profiling

The first step to improving an application’s performance is to profile it—to generate a detailed report of where the time is being spent during execution. Python provides built-in mechanisms for generating code profiles. Cython not only hooks into those mechanisms but has profiling tools of its own.

Python’s own profiler, cProfile, generates reports that show which functions take up the most amount of time in a given Python program. By default, Cython code doesn’t show up in those reports, but you can enable profiling on Cython code by inserting a compiler directive at the top of the .pyx file with functions you want to include in the profiling:

# cython: profile=True

You can also enable line-by-line tracing on the C code generated by Cython, but this imposes a lot of overhead, and so is turned off by default.

Note that profiling imposes a performance hit, so be sure to toggle profiling off for code that is being shipped into production.

Cython can also generate code reports that indicate how much of a given .pyx file is being converted to C, and how much of it remains Python code. To see this in action, edit the setup.py file in our example and add the following two lines at the top:

import Cython.Compiler.Options

Cython.Compiler.Options.annotate = True

(Alternatively, you can use a directive in setup.py to enable annotations, but the above method is often easier to work with.)

.cプロジェクトで生成されたファイルを削除し、setup.pyスクリプトを再実行してすべてを再コンパイルします。完了すると、.pyxファイルの名前(この場合はnum.html。)を共有する同じディレクトリにHTMLファイルが表示されます 。HTMLファイルを開くと、Pythonに依存しているコードの部分が黄色で強調表示されています。黄色の領域をクリックすると、Cythonによって生成された基になるCコードを確認できます。