質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
パフォーマンス

コード効率の向上や計算に関する質問には、このタグを使ってください。

Rust

Rustは、MoFoが支援するプログラミング言語。高速性を維持しつつも、メモリ管理を安全に行うことが可能な言語です。同じコンパイル言語であるC言語やC++では困難だったマルチスレッドを実装しやすく、並行性という点においても優れています。

Q&A

解決済

4回答

1658閲覧

RustでPythonよりもパフォーマンスが出ない

Yhaya

総合スコア439

パフォーマンス

コード効率の向上や計算に関する質問には、このタグを使ってください。

Rust

Rustは、MoFoが支援するプログラミング言語。高速性を維持しつつも、メモリ管理を安全に行うことが可能な言語です。同じコンパイル言語であるC言語やC++では困難だったマルチスレッドを実装しやすく、並行性という点においても優れています。

0グッド

0クリップ

投稿2021/12/03 05:08

編集2021/12/03 06:37

Rustを使った数値計算のプログラムを書いています。その中で、昔書いた Pythonで使う用のコードをRust側で再利用しているのですが、そこでのパフォーマンスがかなり低く、解決方法を探しています。

環境

  • Rust 1.55.0
  • WSL2 (Windows 10)

詳細

Python用に書いたコードは以下のようになっています。

lib.rs

rust

1mod kk; 2 3use pyo3::prelude::*; 4use pyo3::wrap_pyfunction; 5 6use kk::kk::{real2imag_helper, imag2real_helper, kk_transform}; 7 8#[pymodule] 9fn pykk(_py: Python, m: &PyModule) -> PyResult<()> { 10 m.add_wrapped(wrap_pyfunction!(real2imag))?; 11 m.add_wrapped(wrap_pyfunction!(imag2real))?; 12 Ok(()) 13} 14 15#[pyfunction] 16fn real2imag(x: Vec<f64>, y: Vec<f64>) -> PyResult<Vec<f64>> { 17 kk_transform(x, y, real2imag_helper) 18} 19 20/// Calculate the real part from the imaginary part 21/// 22/// * `x` - independent variable (ex. energy or frequency) 23/// * `y` - dependent variable (ex. conductivity or permittivity) 24#[pyfunction] 25fn imag2real(x: Vec<f64>, y: Vec<f64>) -> PyResult<Vec<f64>> { 26 kk_transform(x, y, imag2real_helper) 27}

kk.rs

rust

1use std::sync::{Mutex, Arc}; 2use std::thread; 3use std::f64::consts::PI; 4 5use pyo3::prelude::*; 6use pyo3::exceptions::PyValueError; 7 8pub fn kk_transform<F>(x: Vec<f64>, y: Vec<f64>, f: F) -> PyResult<Vec<f64>> 9 where F: Fn(&Vec<f64>, &Vec<f64>, usize) -> f64, 10 F: Send + Copy + 'static 11{ 12 let thread_num = 16; 13 let mut handles: Vec<thread::JoinHandle<()>> = Vec::new(); 14 let mut result: Arc<Vec<Mutex<f64>>> = Arc::new( 15 vec![0.0; y.len()] 16 .into_iter() 17 .map(|x| Mutex::new(x)) 18 .collect() 19 ); 20 21 for i in 0..thread_num { 22 let x = x.clone(); 23 let y = y.clone(); 24 25 let result = Arc::clone(&mut result); 26 let handle = thread::spawn(move || { 27 for j in x.len()*i/thread_num..x.len()*(i+1)/thread_num { 28 let mut val = result[j].lock().unwrap(); 29 *val = 2.0 / PI * f(&x, &y, j); 30 } 31 }); 32 33 handles.push(handle); 34 } 35 36 for handle in handles { 37 handle.join().unwrap(); 38 } 39 40 let result = Arc::try_unwrap(result).unwrap(); 41 42 let mut output = vec![0.0; y.len()]; 43 for (i, val) in result.iter().enumerate() { 44 output[i] = *val.lock().unwrap(); 45 } 46 47 Ok(output) 48} 49 50#[allow(dead_code)] 51pub fn real2imag_helper(x: &Vec<f64>, y: &Vec<f64>, num: usize) -> f64 { 52 let mut result = 0.0; 53 let diff = x[1] - x[0]; 54 55 let base = x[num]; 56 57 for (xx, yy) in x.iter().zip(y.iter()) { 58 if *xx == base { 59 continue; 60 } 61 result -= base * yy / (xx * xx - base * base) * diff; 62 } 63 64 result 65} 66 67#[allow(dead_code)] 68pub fn imag2real_helper(x: &Vec<f64>, y: &Vec<f64>, num: usize) -> f64 { 69 let mut result = 0.0; 70 let diff = x[1] - x[0]; 71 72 let base = x[num]; 73 74 for (xx, yy) in x.iter().zip(y.iter()) { 75 if *xx == x[num] { 76 continue; 77 } 78 result += xx * yy / (xx * xx - base * base) * diff; 79 } 80 81 result 82}

これをRustで再利用するために、コピペして今書いているプロジェクトのコードに導入しました。

rust

1use std::f64::consts::PI; 2use std::sync::{Arc, Mutex}; 3use std::thread; 4 5pub fn imag2real(x: Vec<f64>, y: Vec<f64>) -> Vec<f64> { 6 kk_transform(x, y, imag2real_helper) 7} 8 9fn kk_transform<F>(x: Vec<f64>, y: Vec<f64>, f: F) -> Vec<f64> 10where 11 F: Fn(&Vec<f64>, &Vec<f64>, usize) -> f64, 12 F: Send + Copy + 'static, 13{ 14 let thread_num = 16; 15 let mut handles: Vec<thread::JoinHandle<()>> = Vec::new(); 16 let mut result: Arc<Vec<Mutex<f64>>> = Arc::new( 17 vec![0.0; y.len()] 18 .into_iter() 19 .map(|x| Mutex::new(x)) 20 .collect(), 21 ); 22 23 for i in 0..thread_num { 24 let x = x.clone(); 25 let y = y.clone(); 26 27 let result = Arc::clone(&mut result); 28 let handle = thread::spawn(move || { 29 for j in x.len() * i / thread_num..x.len() * (i + 1) / thread_num { 30 let mut val = result[j].lock().unwrap(); 31 *val = 2.0 / PI * f(&x, &y, j); // <- ボトルネック!!! 32 } 33 }); 34 35 handles.push(handle); 36 } 37 38 for handle in handles { 39 handle.join().unwrap(); 40 } 41 42 let result = Arc::try_unwrap(result).unwrap(); 43 44 let mut output = vec![0.0; y.len()]; 45 for (i, val) in result.iter().enumerate() { 46 output[i] = *val.lock().unwrap(); 47 } 48 49 output 50} 51 52// ここはPythonで使っていたコードから試行錯誤してすこし変えてみたが根本的な解決 53// にはなっていない 54fn imag2real_helper(x: &Vec<f64>, y: &Vec<f64>, num: usize) -> f64 { 55 let diff = x[1] - x[0]; 56 let base = x[num]; 57 58 let result = x 59 .iter() 60 .zip(y.iter()) 61 .map(|(x, y)| { 62 if *x == base { 63 0.0 64 } else { 65 x * y / (x.powf(2.0) - base.powf(2.0)) * diff 66 } 67 }) 68 .sum(); 69 70 result 71}

これらのコードで速度を比較してみました。比較した関数は,

  • pyfunctionを付けてPython側で使えるようにした imag2real (一番上のコード)
  • Rust用に書いた imag2real (一番下のコード)

です。速度を計測したところ, Rustで使うように書いた imag2realが2桁ほど遅くなっていました。細かく時間を計測してみると imag2real_helper で全体の9割くらいの時間を使っていたことがわかったのですが、どこを改善すれば速くなるのかわかりません。

よろしくお願いいたします。

参考情報

Python拡張として書いたRustコード:
https://github.com/Hayashi-Yudai/pykk

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

melian

2021/12/03 06:20

昔に書いたという Python スクリプトでは Numpy を使っていましたか?
Yhaya

2021/12/03 06:28

いえ、RustオンリーでPython拡張を書いておりまして、その拡張機能で作ったメソッドをPythonで呼び出したときの速度を、質問に載せましたコードと比較すると2桁ほど遅いという問題が起きています。 Rustで作ったPython拡張は https://github.com/Hayashi-Yudai/pykk に公開していますので、この問題の解決の参考になるかわかりませんが、よろしかったら見てください。
melian

2021/12/03 06:31

ありがとうございます。読ませていただきます。
dalance

2021/12/03 08:25

コピペしたコードとのことで、2つのコードはほとんど同じですので この2つを比べて原因を究明するのは難しそうです。 同じコードで速度が違うということは、このコードの外の環境による可能性が高いと思います。 実際に実行して速度比較が可能なリポジトリをご用意いただくのがいいかもしれません。
tatsuya6502

2021/12/03 23:58 編集

何点か質問があるのと、確認してほしいことがあります。 コード内の環境に関係するもの ・imag2real_helper() の内容が kk.rs と一番下のコードで変わってますが、なぜでしょうか? ・一番下のコードの imag2real_helper() の内容を kk.rs と同じにすると、性能差はどうなりますか? コード外の環境に関係するもの ・Python拡張ですが、PyPIで公開されているWindows版(.pyd)を使っているのでしょうか? ・もしそうなら、WSL2のRust 1.55.0でLinux版(.so)をビルドしてPythonから実行すると性能差はどうなりますか?
Yhaya

2021/12/04 00:48

imag2real_helperは上のように元の状態から変えることで少し高速化できています。Linux版のビルドは、これから試してみます
dalance

2021/12/04 01:03

Python版はWindowsネイティブ環境、Rustのみ版はWSL2ということですか? その場合、実行中のWindowsから見たCPU使用率はどうでしょう? どちらも16スレッド分使われていますか?
Yhaya

2021/12/04 02:35

調べていたら、Pythonライブラリとしたコードで、Python内で使うところでバグがあったことが分かりました。修正したところ、どちらのコードも同じくらいの速さであることが分かりました。お騒がせしまして申し訳ありませんでした
tatsuya6502

2021/12/04 02:45

ご報告ありがとうございます。解決してよかったです!
guest

回答4

0

自己解決

質問に書いたコードに間違いはなく、むしろこれを使うコード内にバグあったせいだと分かりました。お騒がせして申し訳ありません

投稿2021/12/04 02:36

Yhaya

総合スコア439

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

https://qiita.com/tatsuya6502/items/d50e4b131130aa5b5ab6
古い情報ですが、Linux環境においてRustのf64powfの性能はかなり低い傾向があるのかもしれません。

投稿2021/12/03 12:20

hmikisato

総合スコア13

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

実行手順の情報がないので詳細はわからないのですが、もしかするとデバッグビルドで実行していないでしょうか?

cargo buildcargo run はデフォルトでデバッグビルドを行います。
リリースビルドにするには cargo build --releasecargo run --release を使います。

投稿2021/12/03 06:12

IgaguriMK

総合スコア148

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Yhaya

2021/12/03 06:29

回答ありがとうございます。リリースビルドであることは確認しております。
guest

0

Python側の呼び出しコード、簡単なデータセットなどがよく分からないのですが、
cargo buildする時に--releaseオプションなどの最適化オプションを付け忘れていそうな気がします。

投稿2021/12/03 05:54

編集2021/12/03 05:57
ncaq

総合スコア11

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Yhaya

2021/12/03 06:03

それは確認してみたのですが、ちゃんと--releaseはつけてました
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問