PYO3 学习4

并行

关于GIL光放文档给出了这样的说法:全局解释器锁 (GIL) 对这种情况有所帮助,它确保只有一个线程可以同时使用 Python 解释器及其 API,而非 Python 操作(系统调用和扩展代码)可以解锁 GIL 。

fn main() {
    let start = Instant::now();
    let py_foo = include_str!("G:/Rust/Rust_Project/rust_senior/try_connnect/import_try.py");
    let k = thread::spawn(||{ let mut h:u64 = 1;while h <= 100000000{ h += 1;}});
    let q = thread::spawn(||{ let mut h:u64 = 1;while h <= 100000000{ h += 1;}});
    // let m = thread::spawn(||{Python::with_gil(|py|{
    //     let func = PyModule::from_code(py,
    //     "def t():
    //                     for i in range(8200000):
    //                         pass
    //                 ",
    //         "",
    //         ""
    //     ).unwrap().getattr("t").unwrap();
    //     func.call0();
    // })});
    
   	// let n =  thread::spawn(||{Python::with_gil(|py|{
    //     let func = PyModule::from_code(py,
    //                                    "def t():
    //                     for i in range(8200000):
    //                         pass
    //                 ",
    //                                    "",
    //                                    ""
    //     ).unwrap().getattr("t").unwrap();
    //     func.call0();
    // })});
    
    k.join();
    q.join();
    // m.join();
    // n.join
    println!("time cost: {:?} ms", start.elapsed().as_millis());// ms
}

我们对这样的一份代码做测试:

k单独运行:  320ms
m单独运行:  345ms
kq一起运行: 368ms
km一起运行: 410ms
mn一起运行: 1137ms

这样的结果说明的问题已经不言而喻,对于两段Python代码只能串行,而Rust的多线程与Python代码线程之间没有必然关系,所以可以并行。

此外, PyO3允许我们传递实现了真并行函数给Python:

// 计算密集型函数
pub fn test_real_cocu(py:Python){
    // 使用并行包需要使用allow_threads方法,该方法接受实现F类型参数,也就是闭包或者函数都接受
    py.allow_threads(||{let mut h = 1; while h <= 900000000{ h += 1;}});
}
from rust_give_python import *
from concurrent.futures import ThreadPoolExecutor
import time

if __name__ == '__main__':
    executor = ThreadPoolExecutor(max_workers=2)
    a = time.time()
    future_1 = executor.submit(
        test_real_cocu,
    )
    future_2 = executor.submit(
        test_real_cocu,
    )
    result_1 = future_1.result()
    result_2 = future_2.result()
    b = time.time()
    print(b-a)
    a = time.time()
    test_real_cocu()
    b = time.time()
    print(b-a)
# 两个线程执行的时间比一个线程快了0.1秒,而且函数是计算密集型的,说明真的并行成功了
2.176496
2.018431

当然,直接用Threading封装也是可以的,结果相差不大,说明Rust真的为Python提供并行包

内存管理

我们都知道Rust语法以及编译器对内存安全要求的严苛程度,没有与编译器抗争过的程序员不是好Rust 而对于Python这种依赖于GC的松散的内存管理结构来说实现共存不是一件容易的事情,虽然我们大部分人的目的不是Python->Rust而是Rust->Python

PYO3 提供了i昂中策略访问Python堆上的内存:GIL绑定的或“拥有”的引用,以及独立GIL的只能指针Py<Any>

GIL绑定内存

PyO3的GIL 绑定的“拥有的引用”(&PyAny等)通过确保 PyO3 的生命周期永远不会超过 Python GIL 的作用域(这一点我之前提到过)这意味着 PyO3 的大部分 API 是在GIL作用域的基础上使用的。

Python::with_gil(|py|{
	let hello: &PyString = py.eval("\"Hello World!\"", None, None)?.extract()?;
})?;

这样的一段代码,我们在上面经常写但实际上暗藏玄机:

with_gil虽然就如同字面意思申请GIL标志或者说加锁,担官网的解释为创建一个 GILPool拥有引用指向的内存的对象 。在GIL作用域内的引用类型生命周期与GILPool绑定(比如本例中的hello),当with_gil()闭包结束或GILGuardfromacquire_gil()被删除时, theGILPool也被删除并且它拥有的变量的 Python 引用计数减少,**将它们释放到 Python 垃圾收集器。**没错,Rust没有释放它,而是交给了Python GC处理,这样我们不得不考虑下面这种情况:

Python::with_gil(|py| -> PyResult<()> {
    for _ in 0..10 {
        let hello: &PyString = py.eval("\"Hello World!\"", None, None)?.extract()?;
        println!("Python says: {}", hello);
    }
    // There are 10 copies of `hello` on Python's heap here.
    Ok(())
})?;

这种情况乍一看 hello 每次创建之后在循环作用域之外就会被清理,实际上hello是一个引用类型,在GIL锁内的引用类型,其生命周期与GILPool绑定,这意味着hello在每次循环后不会被清理掉这也就导致了出现了10个hello 所指向的内存副本存在于内存中,在GIL结束后才释放极大的消耗了内存,这种写法非常不可取

对于这种问题的解决也不是没有办法:

Python::with_gil(|py| -> PyResult<()> {
    for _ in 0..10 {
        let pool = unsafe { py.new_pool() };
        let py = pool.python();
        let hello: &PyString = py.eval("\"Hello World!\"", None, None)?.extract()?;
        println!("Python says: {}", hello);
    }
    Ok(())
})?;

我们使用unsafe方法在循环内嵌套GIL令牌,这样引用变量的生命周期将与新的GIL令牌绑定。然鹅,用了unsafe模块就知道这玩应有点危险指数.

官网给出的原理:当它GILPool被删除时,在此之后创建的所有 PyO3 拥有的引用GILPool都将减少其 Python 引用计数,从而可能允许 Python 删除相应的 Python 对象。PyO3 的高级使用执行长时间运行的任务,从不释放 GIL,可能需要使用此 API 来清除内存,因为 PyO3 通常在释放 GIL 之前不会清除内存。

关于其安全性,可参见:https://pyo3.rs/main/doc/pyo3/marker/struct.Python.html#method.new_pool。

独立于GIL的内存

对于某些特殊的引用类型,或者说智能指针,没错,特指Py<Any>。对Py<Any>的克隆可以增加其引用计数,这意味着它可以比GC更加长寿(话说这词有点怪怪的),例子如下

let hello: Py<PyString> = Python::with_gil(|py| {
        py.eval("\"Hello World!\"", None, None).unwrap().extract()
}).unwrap();
// 我们利用闭包的特性让Python执行hello
Python::with_gil(|py| {
        println!("Python says: {}", hello.as_ref(py));
});
// 这里我们虽然将变量删除,然而Python 堆上的指向内存不会发生任何事情,因为如果我们不持有 GIL,就不会发生任何事情。
// 所以我们drop的操作应该放在GIL里面去进行运作!!
drop(hello); // Memory *not* released here.
// 最后打开GIL是因为hello的引用计数已经为0,让GIL触发Python GC回收内存
Python::with_gil(|py| {}
                     // Memory for `hello` is released here.
);
// 正确写法
let hello: Py<PyString> = Python::with_gil(|py| {
        py.eval("\"Hello World!\"", None, None).unwrap().extract()
}).unwrap();
Python::with_gil(|py| {
        println!("Python says: {}", hello.as_ref(py));
        drop(hello);
});

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Jython是一种用于Java平台的Python实现,而PyO3是一个Python扩展库,它使得可以使用Rust编写Python扩展,因此不能直接使用PyO3开发Jython扩展。不过,Jython本身提供了一些API来编写Java扩展,可以使用这些API来开发Jython扩展。 在Jython中,可以使用Java的反射机制来访问Java类和方法。以下是一个使用Jython和Java互操作的示例: ```python import java import java.lang.reflect.Method # 获取Java String类 string_class = java.lang.String # 创建Java String实例 java_string = string_class("Hello, World!") # 获取Java String的length方法 length_method = string_class.getMethod("length") # 调用length方法 result = length_method.invoke(java_string) # 打印结果 print(result) ``` 在上面的示例中,通过反射机制获取了Java String类和length方法,然后使用Jython调用了Java方法并获取了结果。 在开发Jython扩展时,可以使用类似的方式来调用Java类和方法。需要注意的是,Jython扩展需要编译成Java类,然后可以在Java代码中使用。可以使用Jython提供的命令行工具来编译Jython扩展: ``` jythonc mymodule.py ``` 这将会生成一个Java类文件mymodule.class,然后可以在Java代码中使用它。例如,以下代码演示了如何在Java中调用Jython扩展: ```java import org.python.util.PythonInterpreter; import org.python.core.PyObject; public class MyJavaClass { public static void main(String[] args) { // 创建Python解释器 PythonInterpreter interp = new PythonInterpreter(); // 导入Jython模块 interp.exec("import mymodule"); // 调用Jython函数 PyObject result = interp.eval("mymodule.myfunc('Hello, World!')"); // 打印结果 System.out.println(result); } } ``` 在上面的示例中,使用PythonInterpreter类创建了一个Python解释器,并导入了Jython模块。然后调用了Jython函数并获取了结果。需要注意的是,Jython扩展需要注意和Java互操作的问题,例如类型转换和异常处理等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值