使用rust-cpython从Rust并行运行Python代码

brvekthn  于 2022-11-28  发布在  Python
关注(0)|答案(2)|浏览(344)

我正在尝试使用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);
}
zphenhs4

zphenhs41#

Python的CPython实现不允许同时在多个线程中执行Python bytecode。正如您自己所注意到的,* 全局解释器锁 *(GIL)阻止了这一点。
我们没有任何关于Python代码的确切信息,所以我将给予一些关于如何提高代码性能的一般提示。

  • 如果你的代码是I/O绑定的,例如从网络阅读,你通常会从使用多线程中获得很好的性能改善。阻塞I/O调用会在阻塞之前释放GIL,这样其他线程就可以在这段时间内执行。
  • 一些库,例如NumPy,在不需要访问Python数据结构的长时间运行库调用过程中内部释放GIL。使用这些库,即使你只使用这些库编写纯Python代码,你也可以获得多线程、CPU绑定代码的性能改进。
  • 如果你的代码是CPU绑定的,并且大部分时间都在执行Python字节码,你可以经常使用多个进程而不是线程来实现并行执行,Python标准库中的multiprocessing可以帮助你实现这一点。
  • 如果你的代码是CPU绑定的,大部分时间都在执行Python字节码,* 而且 * 因为它访问共享数据而不能在并行进程中运行,你就不能在多个线程中并行运行它--GIL阻止了这一点。然而,即使没有GIL,你也不能在不改变 * 任何 * 语言的情况下并行运行顺序代码。因为你可以并发访问一些数据,您需要添加锁定并可能进行算法更改以防止数据竞争;具体如何操作取决于您的用例。(如果您 * 不 * 具有并发数据访问,则应该使用进程而不是线程-请参阅上文。)

除了并行性之外,用Rust加速Python代码的一个好方法是 * 剖析 * Python代码,找到花费了大部分时间的 * 热点 *,并 * 重写 * 这些位作为从Python代码中调用的Rust函数。您可以将这种方法与并行性结合起来--与大多数其他语言相比,在Rust中防止数据竞争通常更容易实现。

vfh0ocws

vfh0ocws2#

如果您使用py03绑定,则可以使用allow_threads方法和回调来释放GIL,以实现更快的并行操作:https://pyo3.rs/v0.13.2/parallelism.html

相关问题