みーのぺーじ

みーが趣味でやっているPCやソフトウェアについて.Python, Javascript, Processing, Unityなど.

PyO3でRustのメモリが開放されるのを確認する

Rustで作成したPython用のクラスがPythonのタイミングで自動的にメモリ開放されるかを確認してみました.

環境

  • Windows 10 Pro 64bit
  • CPU : Core i3-7100U CPU @ 2.40GHz
  • Memory : 12.0 GB
  • rustc 1.42.0-nightly (2020-01-17)
  • cargo 1.42.0-nightly (2020-01-13)
  • Python 3.8.1 for windows 10 64bit

実装

Cargo.toml

[package]
name = "pyt"
version = "0.1.0"
authors = ["atsuhiro-me.net"]
edition = "2018"

[lib]
name = "pyt"
crate-type = ["cdylib"]

[dependencies.pyo3]
version = "0.8.5"
features = ["extension-module"]

前回作成したPyTにDrop traitを設定し,メモリ解放されるときに,標準出力にその旨を表示するようにしました.

lib.rs

use pyo3::exceptions;
use pyo3::prelude::*;
use pyo3::types::PyString;

#[pyclass(module = "pyt")]
struct PyT {
    m: String,
}

impl Drop for PyT {
    fn drop(&mut self) {
        println!("[rust] drop: {}", self.m);
    }
}

#[pymethods]
impl PyT {
    #[new]
    fn new(obj: &PyRawObject, d: &PyString) {
        obj.init(PyT {
            m: d.extract::<String>().unwrap(),
        })
    }
    fn get<'p>(&self, py: Python<'p>) -> &'p PyString {
        &PyString::new(py, &self.m)
    }
    fn set(&mut self, d: &PyString) -> PyResult<()> {
        match d.extract::<String>() {
            Ok(u) => {
                self.m = u;
                Ok(())
            }
            Err(_) => Err(exceptions::ValueError::py_err("invalid argument.")),
        }
    }
}

#[pymodule]
fn pyt(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
    m.add_class::<PyT>()?;
    Ok(())
}

これをPythonから呼び出してみます.

import pyt

r = pyt.PyT("apple")
print(r.get())
r.set("banana")
print(r.get())
r = pyt.PyT("orange")
s = r
print(id(r), id(s))
del r
print("r is deleted.")
print(s.get())
apple
banana
[rust] drop: banana
2414632590720 2414632590720
r is deleted.
orange
[rust] drop: orange

まず,set()メソッドで,PyTの中身をappleからbananaに変更しても,PyT自身のメモリは開放されません.

変数rに別のPyT("orange")を代入すると,もともとのPyT("apple")の参照がなくなるので,Pythonのガーベッジコレクションが実行され,PyT("apple")のメモリが開放されます.

変数sにもPyT("orange")の参照を代入し,PyT("orange")が2箇所で参照される状況を作ります.idを確認するとr, sは同一なので,同じインスタンスを参照していることが確認できます.

rを削除します.このとき,PythonではPyT("orange")の参照が1つ残っているため,まだメモリを開放しません.そして,プログラムが終了するときに,PyT("orange")のメモリが開放されます.

PyO3を利用することで,Pythonのガーベッジコレクションとうまく連帯するRustアプリケーションが簡単に作成できることが分かりました.