NumPyでCPUアーキテクチャ特化コードを生成: distutils.ccompiler_opt.CCompilerOpt.try_dispatch()徹底解説


distutils.ccompiler_opt.CCompilerOpt.try_dispatch() は、NumPy の Packaging において、CPU アーキテクチャに特化したコードをコンパイルするための重要な関数です。この関数は、ソースコード内の特殊なコメントを使用して、異なるアーキテクチャ向けのオブジェクトファイルを生成します。

詳細

この関数は以下の機能を提供します。

  • 抽象 C ヘッダーとマクロの生成
    try_dispatch() は、抽象 C ヘッダーとマクロも生成します。これらのヘッダーとマクロは、ランタイム時に適切なオブジェクトファイルを選択するために使用されます。
  • オブジェクトファイルの生成
    @targets コメントに対して、try_dispatch() は対応するオブジェクトファイルを生成します。オブジェクトファイルには、指定された CPU アーキテクチャに特化したコードが含まれます。
  • ソースファイルの分析
    try_dispatch() は、指定されたソースファイルのリストを分析し、@targets コメントを含む行を検出します。これらのコメントは、コンパイル時に使用する CPU アーキテクチャを指定します。

以下の例は、try_dispatch() 関数の使用方法を示しています。

from numpy.distutils.ccompiler_opt import CCompilerOpt

compiler_opt = CCompilerOpt()

sources = [
    "dispatch_source.c",
    "dispatch_source_sse2.c",
    "dispatch_source_avx.c",
]

compiler_opt.try_dispatch(sources)

この例では、dispatch_source.c というソースファイルと、dispatch_source_sse2.cdispatch_source_avx.c というアーキテクチャ固有のソースファイルが指定されています。try_dispatch() 関数は、これらのソースファイルを分析し、@targets コメントに基づいて適切なオブジェクトファイルを生成します。

  • try_dispatch() 関数の詳細については、NumPy のドキュメントを参照してください。
  • この関数は、複雑なコードをより効率的に管理し、異なるアーキテクチャで動作するバイナリを生成するのに役立ちます。
  • try_dispatch() 関数は、NumPy の Packaging において CPU アーキテクチャに特化したコードをコンパイルするための強力なツールです。


from numpy.distutils.ccompiler_opt import CCompilerOpt

compiler_opt = CCompilerOpt()

sources = [
    "dispatch_source.c",
    "dispatch_source_sse2.c",
    "dispatch_source_avx.c",
]

compiler_opt.try_dispatch(sources)
  1. numpy.distutils.ccompiler_opt から CCompilerOpt クラスをインポートします。
  2. CCompilerOpt オブジェクトを作成します。
  3. コンパイルするソースファイルのリストを作成します。このリストには、@targets コメントを含むファイルと、それらのファイルに対応するアーキテクチャ固有のファイルを含めることができます。
  4. try_dispatch() 関数を呼び出し、ソースファイルのリストを渡します。

この関数は、ソースファイルを分析し、@targets コメントに基づいて適切なオブジェクトファイルを生成します。

/* dispatch_source.c */

#include "dispatch_common.h"

@targets("sse2", "avx")
void dispatch_sse2_avx(int n, double *x, double *y) {
  // SSE2 または AVX 命令を使用するコード
}

void dispatch_default(int n, double *x, double *y) {
  // SSE2 または AVX 命令を使用しないコード
}

このコードは、@targets コメントを使用して、異なる CPU アーキテクチャ向けのコードを指定する例を示しています。

  1. dispatch_source.c ファイルには、dispatch_sse2_avx()dispatch_default() という 2 つの関数が定義されています。
  2. dispatch_sse2_avx() 関数は、@targets("sse2", "avx") コメントによって、SSE2 または AVX アーキテクチャ向けのコードであることを示します。
  3. dispatch_default() 関数は、@targets コメントがないため、SSE2 または AVX 命令を使用しないコードであることを示します。

try_dispatch() 関数は、このソースファイルを分析し、以下のオブジェクトファイルを生成します。

  • dispatch_source_avx.o: AVX アーキテクチャでコンパイルされた dispatch_sse2_avx() 関数を含むオブジェクトファイル
  • dispatch_source_sse2.o: SSE2 アーキテクチャでコンパイルされた dispatch_sse2_avx() 関数を含むオブジェクトファイル
  • dispatch_source.o: SSE2 または AVX アーキテクチャでコンパイルされた dispatch_sse2_avx() 関数を含むオブジェクトファイル
/* dispatch_common.h */

#ifdef __SSE2__
#include <immintrin.h>
#endif

#ifdef __AVX__
#include <immintrin.h>
#endif

#ifndef DISPATCH_TARGET_SSE2
#ifndef DISPATCH_TARGET_AVX
#define DISPATCH_TARGET_DEFAULT
#endif
#endif

#ifdef DISPATCH_TARGET_SSE2
#define DISPATCH_FUNC dispatch_sse2_avx
#endif

#ifdef DISPATCH_TARGET_AVX
#define DISPATCH_FUNC dispatch_sse2_avx
#endif

#ifndef DISPATCH_TARGET_SSE2
#ifndef DISPATCH_TARGET_AVX
#define DISPATCH_FUNC dispatch_default
#endif
#endif

このコードは、dispatch_common.h という抽象 C ヘッダーファイルを示しています。このファイルは、try_dispatch() 関数によって生成されたオブジェクトファイルで使用されます。

  1. このファイルは、__SSE2____AVX__ マクロを使用して、現在の CPU アーキテクチャを検出します。
  2. DISPATCH_TARGET_SSE2DISPATCH_TARGET_AVX マクロは、@targets コメントに基づいて定義されます。
  3. DISPATCH_FUNC マクロは、現在の CPU アーキテクチャに基づいて適切な関数名を定義します。


Cython を使用する

Cython は、C/C++ コードを Python に変換するためのツールです。Cython を使用すると、@targets コメントを使用して CPU アーキテクチャに特化したコードを記述することができます。Cython は、try_dispatch() 関数よりも柔軟で強力な機能を提供します。


cdef extern from "dispatch_common.h":
    void dispatch_sse2_avx(int n, double *x, double *y)

@cython.parallel("n", "p")
def dispatch(int n, double *x, double *y, p=1):
    cdef int i
    for i in prange(n):
        if cpuinfo.get_cpu_info().flags["sse2"]:
            dispatch_sse2_avx(i, x, y)
        else:
            dispatch_default(i, x, y)

このコードは、Cython を使用して dispatch() 関数を実装する方法を示しています。この関数は、@targets コメントを使用して CPU アーキテクチャに特化したコードを指定します。

SWIG を使用する


/* dispatch.c */

#include "dispatch_common.h"

extern void dispatch_sse2_avx(int n, double *x, double *y);
extern void dispatch_default(int n, double *x, double *y);

void dispatch(int n, double *x, double *y) {
    if (__builtin_cpu_support_sse2()) {
        dispatch_sse2_avx(n, x, y);
    } else {
        dispatch_default(n, x, y);
    }
}

このコードは、SWIG を使用して dispatch() 関数を実装する方法を示しています。この関数は、__builtin_cpu_support_sse2() マクロを使用して CPU アーキテクチャを検出します。

手動でオブジェクトファイルを生成する

try_dispatch() 関数の代わりに、手動で異なる CPU アーキテクチャ向けのオブジェクトファイルを生成することもできます。これは、より複雑な方法ですが、より柔軟な制御を提供します。


gcc -c -march=native -O3 dispatch_source.c
gcc -c -march=native -O3 -msse2 dispatch_source_sse2.c
gcc -c -march=native -O3 -mavx dispatch_source_avx.c

このコードは、GCC コンパイラを使用して、異なる CPU アーキテクチャ向けのオブジェクトファイルを生成する方法を示しています。

distutils.ccompiler_opt.CCompilerOpt.try_dispatch() は、NumPy の Packaging において CPU アーキテクチャに特化したコードをコンパイルするための便利な機能ですが、いくつかの代替方法も存在します。最適な方法は、プロジェクトの要件とニーズによって異なります。

  • 手動でオブジェクトファイルを生成する方法は、より複雑なプロジェクトに適しています。
  • Cython と SWIG は、NumPy の Packaging においてよく使用されるツールです。