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
回答4件
あなたの回答
tips
プレビュー