1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Rust で Rayon / nalgebra / OpenBLAS を使って行列積を高速化してみた【前回比較あり】

1
Posted at

はじめに

前回の記事「C++ 4種 vs Rust で行列積をベンチマーク比較してみた」では、C++ 4種(naive/OpenMP/Eigen/OpenBLAS)と Rust pure 実装を比較しました。

結果として pure Rust は C++ naive より遅いという結論でした。

「Rust って OpenMP とか Eigen とか OpenBLAS 使えないの?」

そこで今回は、Rust の代替ライブラリを使って同じ土俵で戦えるか検証します。

C++ Rust 代替
OpenMP Rayon
Eigen nalgebra
OpenBLAS cblas FFI

環境

項目 バージョン
OS Ubuntu 22.04 (Dev Container)
rustc / cargo 1.94.1
rayon 1.11.0
nalgebra 0.33.3
cblas 0.4.0
OpenBLAS 0.3.20(システム)

設計の判断

なぜ Rayon は into_par_iter() なのか

OpenMP は #pragma omp parallel for というコンパイラ指示子ですが、Rust にそれはありません。Rayon はイテレータを並列化するライブラリとして同じ役割を果たします。

// 外側ループを並列化(OpenMP の #pragma omp parallel for に相当)
c.par_chunks_mut(n)
    .enumerate()
    .for_each(|(i, row_c)| {
        for k in 0..n {
            let a_ik = a[i * n + k];
            for j in 0..n {
                row_c[j] += a_ik * b[k * n + j];
            }
        }
    });

par_chunks_mut(n) で結果行列を行単位に分割し、各行を並列処理します。
データ競合が型システムで保証されるのが Rust らしい特徴です。

nalgebra の列優先(column-major)に注意

nalgebra は列優先(Fortran 順)でメモリを管理します。Eigen のデフォルト(ColMajor)と同じです。

// from_fn は (row, col) でコールバックを受け取る
let a = DMatrix::<f64>::from_fn(n, n, |_, _| next_f64());
let b = DMatrix::<f64>::from_fn(n, n, |_, _| next_f64());

// * 演算子で行列積(内部で matrixmultiply クレートが SIMD 最適化)
let c = &a * &b;

内部では matrixmultiply クレートが使われており、SIMD 最適化が入ります。

OpenBLAS の FFI と build.rs

Rust から C ライブラリを呼ぶには build.rs でリンク指定が必要です。

// rust/build.rs
fn main() {
    println!("cargo:rustc-link-lib=openblas");
    println!("cargo:rustc-link-search=/usr/lib/x86_64-linux-gnu");
}

実際の呼び出しは unsafe ブロック内で行います:

use cblas::{dgemm, Layout, Transpose};

unsafe {
    dgemm(
        Layout::RowMajor,
        Transpose::None,
        Transpose::None,
        n as i32, n as i32, n as i32,
        1.0,
        a, n as i32,
        b, n as i32,
        0.0,
        c, n as i32,
    );
}

C++ の cblas_dgemm と引数は実質同じです。unsafe が必要な点が唯一の違いです。

ハマりどころ

libopenblas-dev が入らない(前回からの引き続き)

このプロジェクトの Dev Container 環境では libopenblas-devapt install できません。

E: Package 'libopenblas-dev' has no installation candidate

libopenblas0 は入っているので .so シンボリックリンクを手動作成することで解決しました(詳細は前回記事参照)。

sudo ln -sf /usr/lib/x86_64-linux-gnu/libopenblas.so.0 \
            /usr/lib/x86_64-linux-gnu/libopenblas.so

Rayon のスレッドプール初期化コスト

最初の計測が遅くなるケースがあります。Rayon はスレッドプールを遅延初期化するため、1回目の par_iter 呼び出し時にオーバーヘッドが発生します。3回計測して中央値を取る方式が有効です。

ベンチマーク結果

実装 N=512 (ms) N=1024 (ms) N=2048 (ms)
C++ naive 17.61 178.30 2830.72
C++ openmp (4threads) 12.82 91.57 1522.50
C++ eigen 9.40 59.09 475.61
C++ openblas 2.00 26.67 206.46
Rust pure 34.28 310.29 4505.59
Rust + Rayon (4threads) 31.20 264.91 2552.34
Rust + nalgebra 7.64 56.88 425.69
Rust + OpenBLAS 2.42 29.95 177.93

考察

**rust_openblas が全実装中で最速(N=2048: 177ms)**になりました。C++ OpenBLAS の 206ms を上回っています。これはビルドプロファイルの差(lto=true, codegen-units=1)が影響している可能性があります。

rust_nalgebra は C++ Eigen とほぼ同等(425ms vs 476ms)。内部の matrixmultiply クレートが SIMD 最適化を行っており、書き方も &a * &b と簡潔です。

Rayon は N=512 では pure より僅かに速い程度(31ms vs 34ms)。N=2048 では 2552ms vs 4506ms と約 1.8× の改善。C++ OpenMP の 1522ms には及ばないものの、並列化の恩恵は確かにあります。

pure Rust が最も遅いという結果は変わりませんでした。C++ の -march=native による SIMD 自動ベクトル化に対し、Rust の opt-level=3 だけでは追いつかないようです。

対応関係まとめ

C++ Rust 速度比較(N=2048)
naive (2831ms) pure (4506ms) C++ が 1.6× 速い
OpenMP (1523ms) Rayon (2552ms) C++ が 1.7× 速い
Eigen (476ms) nalgebra (426ms) ほぼ同等(Rust が僅かに速い)
OpenBLAS (206ms) rust_openblas (178ms) Rust が 1.2× 速い

まとめ

  • rust_openblas(cblas FFI)は C++ OpenBLAS と同等以上。FFI 呼び出しのオーバーヘッドはほぼゼロ。
  • rust_nalgebra は C++ Eigen と互角matrixmultiply の SIMD 最適化が効いている。
  • Rayon は OpenMP の代替になるが、倍速にはならない。並列化の恩恵はあるが C++ OpenMP ほどではない。
  • pure Rust は C++ に劣る-march=native の SIMD 自動ベクトル化の差が大きい。

Rust で高速な行列演算が必要なら nalgebracblas FFI を使いましょう。

参考

1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?