[用Rust-cpython从Rust并行运行Python代码

问题描述 投票:1回答:1

我正在尝试使用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);
}
python rust cpython
1个回答
0
投票

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

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

  • 如果您的代码受I / O限制,例如从网络上读取数据,使用多个线程通常会获得不错的性能改进。阻塞的I / O调用将在阻塞之前释放GIL,因此其他线程可以在这段时间内执行。

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

  • 如果您的代码受CPU限制并且花费大量时间执行Python字节码,则通常可以使用multipe processes而不是线程来实现并行执行。 Python标准库中的multiprocessing对此提供了帮助。

  • 如果您的代码是CPU绑定的,则花费大部分时间执行Python字节码不能在并行进程中运行,因为它访问共享数据,因此您不能在多个线程中并行运行它– GIL防止了这种情况。但是,即使没有GIL,也不能不更改any语言而并行运行顺序代码。由于您可以同时访问某些数据,因此需要添加锁定并可能进行算法更改以防止数据竞争。具体操作方法取决于您的用例。 (如果您没有具有并发数据访问权限,则应使用进程而不是线程–参见上文。)

除了并行性之外,使用Rust加速Python代码的一种好方法是profile Python代码,找到花费大部分时间的hot spot,然后rewrite这些位作为从Python代码调用的Rust函数。如果这样做不能给您带来足够的提速,您可以combine这种具有并行性的方法–与大多数其他语言相比,在Rust中防止数据竞争通常更容易实现。

© www.soinside.com 2019 - 2024. All rights reserved.