-
Notifications
You must be signed in to change notification settings - Fork 791
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Most efficient way of returning large byte array from Rust to Python #1385
Comments
I see the |
I think there's no way to do this without giving Python control of the allocation. Most of the I think You could also consider |
Sounds like a job for |
Ahhh yes that looks like it! https://docs.python.org/3/c-api/memoryview.html We have some ffi definitions at |
Thanks for the pointers, looks like |
Still learning, but the key to making this work seems to be the buffer protocol which makes it possible for objects to give access to an underlying memory region by implementing a My understanding is that pointers to the |
Yes, implement the I'm still not exactly sure how the lifetimes for |
I think |
BTW, you can make your array readonly: https://docs.rs/numpy/0.13.0/numpy/struct.PyReadonlyArray.html |
sounds like a memory safety nightmare from some C programmers who didn't think about the consequences -- the buffer would have to be |
I think it might be possible to use a |
This sounds like what you want. When the |
I've figured out the remaining details and implemented Basic example/test case: // Rust
#[pyfunction]
fn unicode_buffer() -> arc_buffer::ArcVec {
arc_buffer::ArcVec::new(Arc::new(vec![
80, 121, 79, 51, 32, 70, 84, 87, 33, 32, 240, 159, 166, 128, 240, 159, 166, 128, 240, 159, 166, 128,
]))
} # Python
from pyo3lib import unicode_buffer
import gc
import numpy as np
print("Creating new buffer")
buf = unicode_buffer()
print("Decoding buffer as unicode")
print(bytes(buf).decode('utf-8'))
print("Creating numpy array from buffer")
array = np.frombuffer(buf, np.uint8)
print("Deleting original buffer variable and running gc")
del buf
gc.collect()
print("Deleting numpy array and running gc")
del array
gc.collect()
From what I understand this implementation should be sound, though I may well have missed something. When a C extension wants to obtain access to the memory buffer, it calls |
I'd like to chime in with my use case (and my admittedly much simpler understanding) here that is causing me grief: I'm doing parallel processing in Rust and returning 5x ~150MiB #[pyfunction]
pub fn do_stuff<'a>(py: Python<'a>-> PyResult<&'a PyBytes> {
let start = Instant::now();
let data : Vec<u8> = do_some_stuff();
let result = PyBytes::new(py, &data[..]);
println!("Rust took {} seconds", start.elapsed().as_secs_f32());
Ok(result)
} And I call it from Python: import time
start = time.time()
result = MyLibrary.do_stuff()
end = time.time()
print(f'Python took {end - start} seconds') These times vary by a significant margin: around 70 seconds of Rust time to 78 seconds of Python time, quite consistently. Passing 5x150mb from Python to Rust is instantaneous. In my case, once I pass the memory from Rust to Python or vice-versa, I don't need to maintain the original data. I'm surprised that there's a ~7-8 sec delta between when Rust completes and Python receives the data. Is this something that would be addressed by |
Judging by the example code you've written, the timing in rust is measured after the creation of the Some random stabs in the dark which might help you locate the issue:
|
@davidhewitt - your second point was the key. I assumed that the data structures were being dropped right away when not needed. Manually calling |
This question has been stale for some time now, so I'm going to close it. Thanks for the discussion. |
I'm looking for some guidance on the best way to return a large byte array to Python code and/or share access to raw memory between Rust and Python.
Basically, my application transfers large amounts of data over the network in Rust and then hands the data over to Python (but in some cases I still want concurrent access to the data on the Rust side as well).
One of my ideas is to allocate a numpy array and have the networking code write into it directly, though in that case I don't think the Rust code can still safely access the array because it might be modified concurrently (related question, if I store a reference to a numpy array object on the Rust side, will GC work properly or could the numpy array get deallocated once all references on the Python side disappear?).
I think
PyBytes::from_ptr
might be what I want but I haven't found any documentation on what invariants it expects to be maintained without triggering unsafety.If I have a
Vec<u8>
on the Rust side and return direct access to it withPyBytes::from_ptr
, I assume this is fine as long as I don't deallocate theVec
and the Python code doesn't do anything funky? Is there way to ever safely/automatically deallocate theVec
, or would that require something like exposing afree
method that has to be called explicitly by the Python code after it stops accessing the bytes object?The text was updated successfully, but these errors were encountered: