我正在尝试使用Rust来加速数据管道。管道中包含一些Python代码,我不想修改这些代码,所以我尝试使用rust-cpython和多线程从Rust中按原样运行它们。然而,性能并不是我所期望的,实际上它与在单个线程中按顺序运行Python代码是一样的。
通过阅读文档,我了解到,当调用以下代码时,实际上会得到一个指向单个Python解释器的指针,该解释器只能创建一次,即使您从多个线程分别运行它。
let gil = Python::acquire_gil();
let py = gil.python();
如果是这样的话,这意味着Python GIL实际上也在阻止Rust中的所有并行执行。有没有办法解决这个问题?
下面是我的测试代码:
use cpython::Python;
use std::thread;
use std::sync::mpsc;
use std::time::Instant;
#[test]
fn python_test_parallel() {
let start = Instant::now();
let (tx_output, rx_output) = mpsc::channel();
let tx_output_1 = mpsc::Sender::clone(&tx_output);
thread::spawn(move || {
let gil = Python::acquire_gil();
let py = gil.python();
let start_thread = Instant::now();
py.run("j=0\nfor i in range(10000000): j=j+i;", None, None).unwrap();
println!("{:27} : {:6.1} ms", "Run time thread 1, parallel", (Instant::now() - start_thread).as_secs_f64() * 1000f64);
tx_output_1.send(()).unwrap();
});
let tx_output_2 = mpsc::Sender::clone(&tx_output);
thread::spawn(move || {
let gil = Python::acquire_gil();
let py = gil.python();
let start_thread = Instant::now();
py.run("j=0\nfor i in range(10000000): j=j+i;", None, None).unwrap();
println!("{:27} : {:6.1} ms", "Run time thread 2, parallel", (Instant::now() - start_thread).as_secs_f64() * 1000f64);
tx_output_2.send(()).unwrap();
});
// Receivers to ensure all threads run
let _output_1 = rx_output.recv().unwrap();
let _output_2 = rx_output.recv().unwrap();
println!("{:37} : {:6.1} ms", "Total time, parallel", (Instant::now() - start).as_secs_f64() * 1000f64);
}
2条答案
按热度按时间zphenhs41#
Python的CPython实现不允许同时在多个线程中执行Python bytecode。正如您自己所注意到的,* 全局解释器锁 *(GIL)阻止了这一点。
我们没有任何关于Python代码的确切信息,所以我将给予一些关于如何提高代码性能的一般提示。
multiprocessing
可以帮助你实现这一点。除了并行性之外,用Rust加速Python代码的一个好方法是 * 剖析 * Python代码,找到花费了大部分时间的 * 热点 *,并 * 重写 * 这些位作为从Python代码中调用的Rust函数。您可以将这种方法与并行性结合起来--与大多数其他语言相比,在Rust中防止数据竞争通常更容易实现。
vfh0ocws2#
如果您使用py03绑定,则可以使用allow_threads方法和回调来释放GIL,以实现更快的并行操作:https://pyo3.rs/v0.13.2/parallelism.html