如何在 Rust 中并行创建一个数组并将其 return 到 C#?
How to create an array in Rust in parallel and return it to C#?
我尝试在 Rust 中并行创建一个数组,然后 return 通过 DLL 绑定将其转换为 C#。前 4 个元素无效。
没有 ThreadPool
和同步,我得到了正确的结果。真正的计算比较复杂,但是下面的代码是没有真正计算的简化版。
我也尝试了 int*
而不是 IntPtr
但得到了相同的无效结果。
最后,我是 Rust 的新手,欢迎任何改进代码的建议。
简化的 Rust 计算
#[no_mangle]
pub extern "C" fn create_array(len: libc::c_int, result:*mut *mut libc::c_int){
let mut result_vec: Vec<libc::c_int> = vec![0;len as usize];
let sync_result=Arc::new(Mutex::new(result_vec));
let pool=ThreadPool::new(6);
println!("From Thread");
for i in 0..(len){
pool.execute({
let clone = Arc::clone(&sync_result);
move||{
let mut result_vec = clone.lock().unwrap();
result_vec[i as usize]=i;
if i<10{
println!("{}:{}",i,result_vec[i as usize]);
}
}});
}
pool.join();
let mut result_vec = Arc::try_unwrap(sync_result).unwrap().into_inner().unwrap();
println!("Unwrapped Vector");
for i in 0..10{
println!("{}:{}",i,result_vec[i as usize]);
}
let result_data = result_vec.as_mut_ptr();
unsafe{
println!("Raw data");
*result=result_data;
for i in 0..10 as isize{
println!("{}:{}",i,ptr::read(result_data.offset(i)));
}
}
std::mem::forget(result_data);
}
C# 绑定和函数调用
[DllImport(@"libs\OptimizationRust.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void create_array(int len, out IntPtr result);
public void RustCpuSerial()
{
IntPtr resultPtr;
int len = 10000;
create_array(len,out resultPtr);
int[] results = new int[len];
Marshal.Copy(resultPtr, results, 0, results.Length);
}
Rust 输出:
From Thread
0:0
5:5
7:7
8:8
9:9
1:1
3:3
4:4
6:6
2:2
Unwrapped Vector
0:0
1:1
2:2
3:3
4:4
5:5
6:6
7:7
8:8
9:9
Raw data
0:0
1:1
2:2
3:3
4:4
5:5
6:6
7:7
8:8
9:9
C# 输出:
0:-314008176
1:672
2:-314139296
3:672
4:4
5:5
6:6
7:7
8:8
9:9
任何想法,是什么导致了这种行为?
首先,抱歉,FFI barrier的后半部分是C。我没有合适的环境来展示我的C#技能。
让我们先稍微总结一下您的代码,然后放弃所有线程(因为它什么都不做,只是在问题不存在时让我们感到困惑)
生锈的一半实际上是这样的:
#[no_mangle]
pub extern "C" fn create_array(len: libc::c_int, result:*mut *mut libc::c_int){ // You're passing a pointer to a collection
let mut result_vec: Vec<i32> = vec![]; // You create a Vec<>
for i in 0..(len){
result_vec.push(i); // You fill it...
}
let result_data = result_vec.as_mut_ptr(); // You then get a *mut ptr to it
unsafe{
*result=result_data; // You then assign the content of the pointer-to-a-pointer of what you received to the result ptr you just acquired
}
std::mem::forget(result_data); // And then you forget your data, because it is referenced elsewhere
}
这是在任何更改之前。我添加了评论来总结你最后做了什么。
果然,当从 C:
执行 FFI 时,我可以用这段代码重现错误
这里是 "fixed" 版本:
pub extern "C" fn create_array(len: libc::c_int,result:*mut *mut libc::c_int){
let mut results_vec: Vec<i32> = vec![];
for i in 0..(len) {
results_vec.push(i);
}
let mut result_slice = results_vec.into_boxed_slice(); // Change #1: Vec -> Box<[i32]>
let result_data = result_slice.as_mut_ptr(); // We then get a *mut out of it
unsafe {
*result = result_data; // Same step as yours, we then overwrite the old pointer with its address
// Do note that you may have leaked memory there if the pointer is not null.
for i in 0..10 as isize{
println!("{}:{}",i,ptr::read((*result).offset(i)));
}
}
// We then forget *the boxed slice*. This is the important bit.
std::mem::forget(result_slice);
}
这行得通,至少有少量用 C 编写的测试代码。它行得通而原始版本不行的原因是你没有忘记正确的事情,主要是 - 你忘记了 指向 Vec
的指针,而不是 Vec
本身。结果,整个内存块在技术上未初始化,显然在 FFI 使用和打印数据之间用于其他用途。
实际上,您将调用 Box::into_raw(result_slice)
,而不是调用 result_slice.as_mut_ptr()
,这样的好处是您不必记住 forget
切片。
我尝试在 Rust 中并行创建一个数组,然后 return 通过 DLL 绑定将其转换为 C#。前 4 个元素无效。
没有 ThreadPool
和同步,我得到了正确的结果。真正的计算比较复杂,但是下面的代码是没有真正计算的简化版。
我也尝试了 int*
而不是 IntPtr
但得到了相同的无效结果。
最后,我是 Rust 的新手,欢迎任何改进代码的建议。
简化的 Rust 计算
#[no_mangle]
pub extern "C" fn create_array(len: libc::c_int, result:*mut *mut libc::c_int){
let mut result_vec: Vec<libc::c_int> = vec![0;len as usize];
let sync_result=Arc::new(Mutex::new(result_vec));
let pool=ThreadPool::new(6);
println!("From Thread");
for i in 0..(len){
pool.execute({
let clone = Arc::clone(&sync_result);
move||{
let mut result_vec = clone.lock().unwrap();
result_vec[i as usize]=i;
if i<10{
println!("{}:{}",i,result_vec[i as usize]);
}
}});
}
pool.join();
let mut result_vec = Arc::try_unwrap(sync_result).unwrap().into_inner().unwrap();
println!("Unwrapped Vector");
for i in 0..10{
println!("{}:{}",i,result_vec[i as usize]);
}
let result_data = result_vec.as_mut_ptr();
unsafe{
println!("Raw data");
*result=result_data;
for i in 0..10 as isize{
println!("{}:{}",i,ptr::read(result_data.offset(i)));
}
}
std::mem::forget(result_data);
}
C# 绑定和函数调用
[DllImport(@"libs\OptimizationRust.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void create_array(int len, out IntPtr result);
public void RustCpuSerial()
{
IntPtr resultPtr;
int len = 10000;
create_array(len,out resultPtr);
int[] results = new int[len];
Marshal.Copy(resultPtr, results, 0, results.Length);
}
Rust 输出:
From Thread
0:0
5:5
7:7
8:8
9:9
1:1
3:3
4:4
6:6
2:2
Unwrapped Vector
0:0
1:1
2:2
3:3
4:4
5:5
6:6
7:7
8:8
9:9
Raw data
0:0
1:1
2:2
3:3
4:4
5:5
6:6
7:7
8:8
9:9
C# 输出:
0:-314008176
1:672
2:-314139296
3:672
4:4
5:5
6:6
7:7
8:8
9:9
任何想法,是什么导致了这种行为?
首先,抱歉,FFI barrier的后半部分是C。我没有合适的环境来展示我的C#技能。
让我们先稍微总结一下您的代码,然后放弃所有线程(因为它什么都不做,只是在问题不存在时让我们感到困惑)
生锈的一半实际上是这样的:
#[no_mangle]
pub extern "C" fn create_array(len: libc::c_int, result:*mut *mut libc::c_int){ // You're passing a pointer to a collection
let mut result_vec: Vec<i32> = vec![]; // You create a Vec<>
for i in 0..(len){
result_vec.push(i); // You fill it...
}
let result_data = result_vec.as_mut_ptr(); // You then get a *mut ptr to it
unsafe{
*result=result_data; // You then assign the content of the pointer-to-a-pointer of what you received to the result ptr you just acquired
}
std::mem::forget(result_data); // And then you forget your data, because it is referenced elsewhere
}
这是在任何更改之前。我添加了评论来总结你最后做了什么。
果然,当从 C:
执行 FFI 时,我可以用这段代码重现错误这里是 "fixed" 版本:
pub extern "C" fn create_array(len: libc::c_int,result:*mut *mut libc::c_int){
let mut results_vec: Vec<i32> = vec![];
for i in 0..(len) {
results_vec.push(i);
}
let mut result_slice = results_vec.into_boxed_slice(); // Change #1: Vec -> Box<[i32]>
let result_data = result_slice.as_mut_ptr(); // We then get a *mut out of it
unsafe {
*result = result_data; // Same step as yours, we then overwrite the old pointer with its address
// Do note that you may have leaked memory there if the pointer is not null.
for i in 0..10 as isize{
println!("{}:{}",i,ptr::read((*result).offset(i)));
}
}
// We then forget *the boxed slice*. This is the important bit.
std::mem::forget(result_slice);
}
这行得通,至少有少量用 C 编写的测试代码。它行得通而原始版本不行的原因是你没有忘记正确的事情,主要是 - 你忘记了 指向 Vec
的指针,而不是 Vec
本身。结果,整个内存块在技术上未初始化,显然在 FFI 使用和打印数据之间用于其他用途。
实际上,您将调用 Box::into_raw(result_slice)
,而不是调用 result_slice.as_mut_ptr()
,这样的好处是您不必记住 forget
切片。