Raku NativeCall 到 Rust FFI 的 CArray 大小限制 - SegFault

6
以下是我的代码的最小可重现示例,旨在对WIP Raku Module Dan::Polars进行压力测试/基准测试。
在Rust中,我使用以下代码来创建一个libmre.so库。
  1 use libc::c_char;
  2 use libc::size_t;
  3 use std::slice;
  4 use std::ffi::*; //{CStr, CString,}
  5 
  6 //  Container
  7 
  8 pub struct VecC {
  9     ve: Vec::<String>,
 10 }   
 11 
 12 impl VecC {
 13     fn new(data: Vec::<String>) -> VecC               
 14     {   
 15         VecC {
 16             ve: data,
 17         }   
 18     }   
 19     
 20     fn show(&self) {
 21         println!{"{:?}", self.ve};
 22     }   
 23 }   
 24     
 25 #[no_mangle]
 26 pub extern "C" fn ve_new_str(ptr: *const *const c_char, len: size_t)                        
 27     -> *mut VecC {    
 28     
 29     let mut ve_data = Vec::<String>::new();
 30     unsafe {
 31         assert!(!ptr.is_null());
 32         
 33         for item in slice::from_raw_parts(ptr, len as usize) {
 34             ve_data.push(CStr::from_ptr(*item).to_string_lossy().into_owned());
 35         };  
 36     };  
 37     
 38     Box::into_raw(Box::new(VecC::new(ve_data)))
 39 }   
 40 
 41 #[no_mangle]
 42 pub extern "C" fn ve_show(ptr: *mut VecC) {
 43     let ve_c = unsafe {
 44         assert!(!ptr.is_null());
 45         &mut *ptr
 46     };  
 47     
 48     ve_c.show();
 49 }  

还有这个Cargo.toml

  1 [package]
  2 name = "mre"
  3 version = "0.1.0"
  4 edition = "2021"
  5 
  6 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
  7 
  8 [dependencies]
  9 libc = "0.2.126"
 10 
 11 [lib]
 12 name = "mre"
 13 path = "src/lib.rs"
 14 crate-type = ["cdylib"]

在 Raku 中,我这样使用 libmre.so。
  1 #!/usr/bin/env raku
  2 use lib '../lib';
  3 
  4 use NativeCall;
  5 
  6 #my $output;    #mre tried to move decl to here to avoid going out of scope
  7 sub carray( $dtype, @items ) {
  8     my $output := CArray[$dtype].new();
  9     loop ( my $i = 0; $i < @items; $i++ ) {
 10         $output[$i] = @items[$i]
 11     }
 12     say $output;
 13     $output
 14 }   
 15     
 16 ### Container Classes that interface to Rust lib.rs ###
 17 
 18 constant $n-path    = '../mre/target/debug/mre';
 19 
 20 class VecC is repr('CPointer') is export {
 21     sub ve_new_str(CArray[Str],size_t) returns VecC is native($n-path) { * }
 22     sub ve_show(VecC) is native($n-path) { * }
 23     
 24     method new(@data) { 
 25         ve_new_str(carray(Str, @data), @data.elems );
 26     }   
 27     
 28     method show {
 29         ve_show(self)
 30     }   
 31 }   
 32 
 33 my \N = 100;   #should be 2e9  #fails between 30 and 100
 34 my \K = 100;   
 35 
 36 sub randChar(\f, \numGrp, \N) {
 37     my @things = [sprintf(f, $_) for 1..numGrp];
 38     @things[[1..numGrp].roll(N)];
 39 }   
 40 
 41 my @data = [randChar("id%03d", K, N)];
 42 
 43 my $vec = VecC.new( @data );
 44 $vec.show;

当 \N <30 时,此代码可以正常运行,并会输出以下结果:

NativeCall::Types::CArray[Str].new
["id098", "id035", "id024", "id067", "id051", "id025", "id024", "id092", "id044", "id042", "id033", "id004", "id100", "id091", "id087", "id059", "id031", "id063", "id019", "id035"]

当 \N 大于50时,我会遇到以下问题:

NativeCall::Types::CArray[Str].new
Segmentation fault (core dumped)

这是:

Welcome to Rakudo™ v2022.04.
Implementing the Raku® Programming Language v6.d.
Built on MoarVM version 2022.04.
on ubuntu

由于基准调用要求 \N 为2e9,我需要一些帮助来尝试解决这个问题。

如果您想在家中尝试,则可以使用 Docker Hub 上的 p6steve/raku-dan:polars-2022.02-arm64(或-amd64)。别忘了首先运行 cargo build。其中包括 RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | bash -s -- -y


1
OOC,如果30 <= N <= 50会发生什么?大多数情况下它是否可以正常运行,而在N值更高时是否更容易出现segfault错误?如果是这样,那么很可能是内联/ spesh / JIT问题。在这种情况下,您可以尝试使用MVM_SPESH_DISABLE = 1运行,以查看是否有所不同。 - Elizabeth Mattijsen
1
感觉这需要一个Rakudo问题。 - Elizabeth Mattijsen
如果我是你,我也不会用Raku生成2e9个字符串。这需要很长时间。Rust可以更快地处理这个问题。现在我想想,也许干脆放弃Raku吧。 - gmoshkin
2
@gmoshkin 我认为p6steve正在追求两全其美的最佳选择 :-) - Elizabeth Mattijsen
@ElizabethMattijsen - 是的,MVM_SPESH_DISABLE=1 可以解决问题 - 我会记录一个问题。短期问题已解决。Str 上的 2e5 次迭代需要 2.674 秒。 - librasteve
显示剩余6条评论
1个回答

7

randChar函数中存在一个偏移一位的bug。

sub randChar(\f, \numGrp, \N) {
    my @things = [sprintf(f, $_) for 1..numGrp];     
    @things[[1..numGrp].roll(N)];
}   

您正在使用从 1numGrp 的索引对 @things 数组进行索引,但是 @things 的最大索引为 numGrp - 1。因此,在返回的数组元素中,有时会出现一个或多个元素中的字符串被替换成了 (Any) 。想要实现的效果应该是这样的:
sub randChar(\f, \numGrp, \N) {
    my @things = [sprintf(f, $_) for 1..numGrp];     
    @things.roll(N); # call roll directly on @things
}   

太棒了,发现了问题!(脸红) - librasteve
2
就我个人而言,我的 MBA(M1)现在可以在 6.7 秒内完成 100 万次迭代(然后就会超出 RAM)。因此,如果还没有准备好处理 50GB 的数据集,那么对于 0.5GB 的数据集来说,这是可以接受的。 - librasteve

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接