LoginSignup
9
10

More than 3 years have passed since last update.

Pythonを爆速にする「Cython」チュートリアル: C++のコードがライブラリに依存しているとき。まずはCMake。

Last updated at Posted at 2020-03-16

概要

C++ で書いているコードを、どうしてもPythonで呼べる形のライブラリにしたい。
ただ、そこにはCythonという大きな壁が立ちはだかっていた。

これは、Pythonを爆速にする「Cython」チュートリアル: C++のコードがライブラリに依存しているとき。準備編 の続きになります。

コードは「ここのgithub」にあげてあるので、ぜひご覧ください。

ミニマムなフォルダ構成として、こんな風になっていました。

状況説明

(myenv) user@~/Documents/cython_practice[master]> tree .
.
├── README.md
├── cpp_library
│   ├── TestClass1.cpp
│   └── TestClass1.h
├── cython
│   ├── my_library.pxd
│   ├── my_library.pyx
│   ├── test_class1.pxd
│   └── test_class1.pyx
└── setup.py

さらに前回の最後に、

wget https://gmplib.org/download/gmp/gmp-6.1.2.tar.xz &&\
tar xvf gmp-6.1.2.tar.xz &&\
cd gmp-6.1.2 &&\
./configure --prefix=/usr/local/gmp/6_1_2 &&\
make && make check && make install

として、gmpライブラリをインストールしました。

今回何をするか

今回は、まず

  1. gmpライブラリを使った簡単なコードを書き、コマンドでコンパイルできることを確かめる。
  2. そもそもC++のライブラリがどのようにインストールされており、どのように参照しているのか。
  3. 依存関係を持つプログラムのコンパイルはCMakeを使ってどのように実行されるのか。
  4. それがcython化するときにsetup.pyにどのように書かれるのか。

の、3まで解説していきます。
4に関しては、実際にCythonを使い依存関係を持ったC++プログラムをPythonから呼べるようにコンパイルしていきますが、それは次の記事で書きます。

C++の依存関係

そもそもC++のライブラリがどのようにインストールされており、どのように参照しているのか。

gmp ライブラリをインストールした時のコマンドですが、

./configure --prefix=/usr/local/gmp/6_1_2

これは、のちのmakeでコンパイルした際にできたオブジェクトファイル、またプロジェクト内の大元となっているヘッダーファイルを、make installの際にどこに格納するか指定しているコマンドになります。

もし指定しない場合、オブジェクトファイルは基本的には/usr/local/libに保管され、必要なヘッダーファイル(基本的にライブラリに必要なプログラムファイルを全て含んだ一つのヘッダーファイル)は/usr/local/includeに保管されます。

この後、makeによりコンパイルが行われ、make installによりコンパイルされたオブジェクトファイル、ヘッダーファイルが格納されます。

ここでは、実際にgmpライブラリのオブジェクトファイル、ヘッダーファイルが所定のフォルダに格納されていることを確かめます。

root@e96f489c2395:/# ls /usr/local/gmp/6_1_2/lib/
libgmp.a  libgmp.la  libgmp.so  libgmp.so.10  libgmp.so.10.3.2
root@e96f489c2395:/# ls /usr/local/gmp/6_1_2/include/
gmp.h

たしかに、オブジェクトファイルが/usr/local/gmp/6_1_2/lib 下に、
ヘッダーファイルが/usr/local/gmp/6_1_2/include下に格納されています。

cmake

依存関係を持つプログラムのコンパイルはCMakeを使ってどのように実行されるのか。

したがって、これらを使用するプログラムを作成し、コンパイルするためには、これらを参照するようにしてコンパイルする必要があるのです。
まずは、単純にg++を用い、簡単なプログラムを書き、それをコンパイルして見ます。

test.cpp
#include <iostream>
#include <gmp.h>

using namespace std;

void print_test(){
    mpz_t test;
    mpz_init(test);
    mpz_set_ui(test, 1);
    gmp_printf("print : %Zd \n", test);
}

int main(){
    print_test();
}


gmpライブラリについて知る必要はないですが、gmpは大きな整数の計算をするライブラリなので、これは単純にtestというオブジェクトに1を代入し、プリントするだけのプログラムです。

オプションを使ってライブラリを明示し、コンパイルします。もちろん、何もライブラリについて書かずにコンパイルしようとすると、参照エラーが出ます。

root@e96f489c2395:/from_local# g++ test.cpp
/tmp/ccZnVmvP.o: In function `main':
test.cpp:(.text+0x1f): undefined reference to `__gmpz_init'
test.cpp:(.text+0x30): undefined reference to `__gmpz_set_ui'
test.cpp:(.text+0x48): undefined reference to `__gmp_printf'
collect2: error: ld returned 1 exit status

となります。これはもちろん依存ライブラリを明示していないからですが、それを解決することで、

root@e96f489c2395:/from_local# g++ test.cpp -L/usr/local/gmp/6_1_2/lib -I/usr/local/gmp/6_1_2/include -lgmp
root@e96f489c2395:/from_local# ./a.out
print : 1 

のようにもちろんコンパイルできます。
この場合、-Lはライブラリ(オブジェクトファイル)へのパスを明示しているものであり、
-Iはヘッダーファイルへのパスを明示するコンパイルオプションです。

しかしながら、依存するライブラリが複数ある時や、書いているプログラムが複数に渡る時、このようにg++でコンパイルするのはかなり面倒なことになります。そこで、CMakeLists.txtを用意し、コンパイルを簡単に行います。

それを実行するために、以下のようなCMakelists.txtを作り、
cmake . && makeを使ってコンパイルするのが望ましいです。

ここでは、CMakelists.txtを次のように作成し、コマンドでg++とオプションを使ってコンパイルする代わりに、cmake . && makeを使ってコンパイルしてみます。

CMakeLists.txt
cmake_minimum_required(VERSION 3.10)

project(TEST VERSION 1.1.0 LANGUAGES CXX)

# Executable will be in ../bin
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR})
set(CMAKE_CXX_FLAGS "-g -O0 -lgmp")


add_executable(test1 
    test.cpp
    )

target_sources(test1
    PRIVATE
)

target_include_directories(test1 PRIVATE /usr/local/gmp/6_1_2/include/)
target_link_libraries(test1 gmp /usr/local/gmp/6_1_2/lib)

root@e96f489c2395:/from_local# cmake .
-- Configuring done
WARNING: Target "test1" requests linking to directory "/usr/local/gmp/6_1_2/lib".  Targets may link only to libraries.  CMake is dropping the item.
-- Generating done
-- Build files have been written to: /from_local
root@e96f489c2395:/from_local# make 
[100%] Built target test1
root@e96f489c2395:/from_local# ./test1
print : 1 

まとめ

C++のプログラムをcython化するときに必要な知識として、以下のことを行い、C++側で依存関係をもつプログラムのcython化を達成した。

  • C++のプログラムを書くときにつまづきやすい、依存関係について解説した。
  • CMakeを使ったC++のコンパイルについて解説した。

次の記事では、
- 依存関係を踏まえたsetup.pyの書き方について解説します。

今回はこの辺で。

おわり。

9
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
10