2020年9月26日土曜日

xtensor

C++でもnumpyっぽく使えるxtensorを試してみます。

インストール

まずは、インストール。次のスクリプトを実行します。

#!/bin/bash
git clone https://github.com/xtensor-stack/xtensor.git
git clone https://github.com/xtensor-stack/xtl.git
git clone https://github.com/xtensor-stack/xsimd.git
git clone https://github.com/xtensor-stack/xtensor-blas.git

INSTALL_PATH=$(pwd)/root
for x in xtl xsimd xtensor xtensor-blas
do
    echo "-------- $x ----------"
    cd $x
    mkdir -p build
    cd build
    cmake -DCMAKE_INSTALL_PREFIX=$INSTALL_PATH ..
    make install
    cd ../..
done

行列積の計算時間

行列積の計算時間を測定してみます。測定する処理を書いたC++のコードは次の通り。10000x10000の2つの行列の積を計算し、その後、平均値を計算します。これを5回繰り返します。なお、平均値は10000になります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include "xtensor.hpp"
#include "xtensor-blas/xlinalg.hpp"

void dot(void){
  xt::xarray<float> a = xt::ones<float>({10000, 10000});
  xt::xarray<float> b = xt::ones<float>({10000, 10000});
  for(int i=0; i<5; ++i){
    auto d = xt::linalg::dot(a, b);
    auto c = xt::average(d);
    std::cout << c << std::endl;
  }
}

int main(void){
  dot();
  return 0;
}
このファイルをdot.cppとして保存し、次のようなCMakeLists.txtを作成します。xtensor_pathの値は適切なものに書き換えてください。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
cmake_minimum_required(VERSION 3.10)
project(xtexample VERSION 1.0)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED True)

add_definitions(-DHAVE_CBLAS=1)
find_package(BLAS REQUIRED)
find_package(LAPACK REQUIRED)

add_executable(xtexample dot.cpp)
set(XTENSOR_USE_XSIMD ON)
set(xtensor_path "/path/to/xtensor/root")
set(xtensor_DIR "${xtensor_path}/lib/cmake/xtensor")
set(xtl_DIR "${xtensor_path}/lib/cmake/xtl")
set(xsimd_DIR "${xtensor_path}/lib/cmake/xsimd")
find_package(xtensor REQUIRED)
target_include_directories(xtexample PUBLIC ${xtensor_INCLUDE_DIRS})
target_link_libraries(xtexample PUBLIC xtensor xtensor xtensor::optimize xtensor::use_xsimd
                      ${BLAS_LIBRARIES} ${LAPACK_LIBRARIES})
ビルドします。
$ mkdir build
$ cd build
$ cmake -DCMAKE_BUILD_TYPE=Release ..
$ make
処理時間を測定します。
$ for _ in {1..4}; do time ./xtexample; done
結果は次の通りです。4コア8スレッドのCPUで測定しています。
Trial #realusersys
10m43.805s5m17.723s0m23.991s
20m46.952s5m44.626s0m21.066s
30m49.060s5m59.719s0m22.163s
40m50.819s6m9.617s0m25.812s

Python numpyとの比較

比較のため、Pythonのnumpyで同じことをしてみます。測定コードは以下の通り。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import numpy as np

def dot():
    a = np.ones((10000, 10000), dtype=np.float32)
    b = np.ones((10000, 10000), dtype=np.float32)
    for i in range(5):
        d = np.dot(a, b)
        c = np.average(d)
        print(c)

dot()
C++の場合と同様に実行します。
for _ in {1..4}; do time python3 comp.py; done
結果は次の通り。
Trial #realusersys
10m44.816s5m29.361s0m17.726s
20m49.529s6m5.012s0m21.217s
30m49.188s5m58.570s0m24.681s
40m50.497s6m11.620s0m22.015s
C++と同じくらいの結果となりました。

まとめ

C++でnumpyのように行列計算を使いたい場合は、xtensorを使えばよさそうです。 処理速度に関しても、少なくとも行列積に関してはnumpyに比べて遅いわけではないようです。