使用Rust和rustcpython并行运行Python代码

2024-10-02 22:35:36 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在尝试使用Rust加速数据管道。管道包含一些我不想修改的Python代码,因此我尝试使用rust-cpython和多个线程按原样运行它们。 但是,性能并不是我所期望的,它实际上与在单个线程中顺序运行python代码位相同

在阅读文档时,我了解到在调用以下命令时,实际上会得到一个指向单个Python解释器的指针,该解释器只能创建一次,即使您分别从多个线程运行它

    let gil = Python::acquire_gil();
    let py = gil.python();

如果是这样的话,这意味着pythongil实际上也在防止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);
}

Tags: pynoneoutputtimeparallelusethreadstart
1条回答
网友
1楼 · 发布于 2024-10-02 22:35:36

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

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

  • 如果您的代码是I/O绑定的,例如从网络读取,那么使用多个线程通常会获得很好的性能改进。阻塞I/O调用将在阻塞之前释放GIL,因此其他线程可以在此期间执行

  • 一些库,例如NumPy,在不需要访问Python数据结构的长时间运行库调用期间,在内部释放GIL。有了这些库,即使只使用该库编写纯Python代码,也可以提高多线程、CPU绑定代码的性能

  • 如果您的代码受CPU限制,并且大部分时间都在执行Python字节码,那么您通常可以使用多个进程而不是线程来实现并行执行。Python标准库中的^{}有助于实现这一点

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

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

相关问题 更多 >