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

2024-04-07

我正在尝试使用 Rust 加速数据管道。该管道包含一些我不想修改的 Python 代码,因此我尝试使用 Rust 按原样运行它们rust-cpython https://docs.rs/cpython/0.4.1/cpython/和多线程。 然而,性能并没有达到我的预期,它实际上与在单个线程中顺序运行 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);
}

Python 的 CPython 实现不允许执行 Pythonbytecode同时在多个线程中。正如您自己所注意到的,全局解释器锁(GIL)可以防止这种情况发生。

我们没有任何关于您的 Python 代码到底在做什么的信息,因此我将提供一些如何提高代码性能的一般提示。

  • 如果您的代码受 I/O 限制,例如从网络读取数据,通常可以通过使用多线程获得很好的性能改进。阻塞 I/O 调用将在阻塞之前释放 GIL,以便其他线程可以在此期间执行。

  • 一些图书馆,例如NumPy,在不需要访问 Python 数据结构的长时间运行的库调用期间内部释放 GIL。借助这些库,即使您只使用该库编写纯 Python 代码,也可以获得多线程、CPU 密集型代码的性能改进。

  • 如果您的代码受 CPU 限制并且大部分时间都在执行 Python 字节码,则通常可以使用多个流程而不是线程来实现并行执行。这multiprocessing https://docs.python.org/3/library/multiprocessing.htmlPython 标准库中的内容对此有所帮助。

  • 如果您的代码受 CPU 限制,则大部分时间都花在执行 Python 字节码上and不能在并行进程中运行,因为它访问共享数据,您不能在多个线程中并行运行它 – GIL 阻止了这一点。然而,即使没有 GIL,您也不能只并行运行顺序代码而不更改any语言。由于您可以并发访问某些数据,因此您需要添加锁定并可能进行算法更改以防止数据竞争;如何执行此操作的详细信息取决于您的用例。 (如果你don't有并发数据访问,您应该使用进程而不是线程 - 见上文。)

除了并行性之外,使用 Rust 加速 Python 代码的一个好方法是profile你的Python代码,找到热点大部分时间都花在哪里,以及rewrite这些位是您从 Python 代码中调用的 Rust 函数。如果这不能给你足够的加速,你可以combine这种并行性方法——防止数据竞争通常在 Rust 中比在大多数其他语言中更容易实现。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 rust-cpython 从 Rust 并行运行 Python 代码 的相关文章

随机推荐