如何some_math
有效地实施?
您可以使用mapv_into()
:
use ndarray as nd;
use ndarray::Array2;
fn some_math(matrix: Array2<f64>) -> Array2<f64> {
// np.sqrt(np.exp(matrix)) would literally translate to equivalent to
// matrix.mapv_into(f64::exp).mapv_into(f64::sqrt)
// but this version iterates over the matrix just once
matrix.mapv_into(|v| v.exp().sqrt())
}
fn main() {
let matrix = nd::array![[1., 2., 3.], [9., 8., 7.]];
let result = some_math(matrix);
println!("{:?}", result)
}
操场
这应该给你的性能与 的相当numpy
,但你应该确定。
要使用对大型阵列有意义的多核,您需要启用rayon
crate 的功能并使用par_mapv_inplace()
:
fn some_math(mut matrix: Array2<f64>) -> Array2<f64> {
matrix.par_mapv_inplace(|v| v.exp().sqrt());
matrix
}
(不在 Playground 上编译,因为 Playgroundndarray
不包含该rayon
功能。)
请注意,在上面的示例中,如果感觉更自然,您可以替换v.exp().sqrt()
为f64::sqrt(f64::exp(v))
。
编辑:我对时间很好奇,所以我决定做一个微不足道的(和不科学的)基准测试——创建一个随机的 10_000x10_000 数组并np.sqrt(np.sqrt(array))
与 Rust 等价物进行比较。
用于基准测试的 Python 代码:
import numpy as np
import time
matrix = np.random.rand(10000, 10000)
t0 = time.time()
np.sqrt(np.exp(matrix))
t1 = time.time()
print(t1 - t0)
锈代码:
use std::time::Instant;
use ndarray::Array2;
use ndarray_rand::{RandomExt, rand_distr::Uniform};
fn main() {
let matrix: Array2<f64> = Array2::random((10000, 10000), Uniform::new(0., 1.));
let t0 = Instant::now();
let _result = matrix.mapv_into(|v| v.exp().sqrt());
let elapsed = t0.elapsed();
println!("{}", elapsed.as_secs_f64());
}
在我对古老桌面系统的实验中,Python 需要3.7 秒来计算,而 Rust 需要2.5 秒。替换mapv_into()
为par_mapv_inplace()
使 Rust 速度大大加快,现在的时钟速度为0.5 s,比同等 Python 快 7.4 倍。
单线程 Rust 版本更快是有道理的,因为它只迭代整个数组一次,而 Python 会迭代两次。如果我们去掉这个sqrt()
操作,Python 的时钟是 2.8 秒,而 Rust 在 2.4 秒时仍然稍微快一些(并且仍然是 0.5 秒并行)。我不确定是否可以在不使用 numba 之类的东西的情况下优化 Python 版本。事实上,在不因手动进行低级计算而导致性能损失的情况下调整代码的能力是像 Rust 这样的编译语言的好处。
多线程版本是我不知道如何在 Python 中复制的东西,但是知道 numba 的人可以做到并进行比较。