从Raku向C传递数值数组的高效方法探究
Raku与C高效传递数值数组的优化方案探讨
问题背景
现有演示代码目标是实现Raku向C传递数值数组,通过OpenMP并行执行元素加法后返回结果到Raku,但当前实现因类型转换开销导致性能未超过Raku原生代码。
C实现代码
#include <stdio.h> #include <omp.h> void c_sum_two(double *first, double *second, double *third, int num) { #pragma omp parallel for for (int i = 0; i < num; i++) { third[i] = first[i] + second[i]; } }
该代码接收两个double类型数组,通过OpenMP并行计算元素和,结果存入第三个数组。
当前Raku实现代码
use NativeCall; constant MYDYN = "./testsum.dll"; sub c_sum_two(CArray[num64], CArray[num64], CArray[num64], int32) is native(MYDYN) { * }; sub native_sum_two(@arr_one is copy, @arr_two is copy) { my $elems = @arr_one.elems; # 检查两个数组长度是否一致 # 准备传递给C的数组 @arr_one = @arr_one.map: {$_.Num}; @arr_two = @arr_two.map: {$_.Num}; # 处理第一个数组 my $first = CArray[num64].new(@arr_one); # 处理第二个数组 my $second = CArray[num64].new(@arr_two); # 准备返回数组 my $return = CArray[num64].allocate($elems); # 调用C函数 c_sum_two($first, $second, $return, $elems); # 获取结果 $return.list } my @a = <1.2 2 3.2 10 0.33>; my @b = <2 12 18 10 8>; say native_sum_two(@a, @b); # 输出 (3.2 14 21.2 20 8.33)
当前实现需将数组元素逐个转换为Num,带来额外O(n)开销,导致性能不如Raku原生代码。
Raku原生实现代码
my @c; for 0..@a.elems-1 { @c[$_] = @a[$_] + @b[$_]; } say @c;
待解决问题
- 当前实现是否为高效的正确做法?
- 是否存在直接将数组按数值类型布局在内存中的方法?
- 有没有更优的数组返回方式?
问题解答
1. 当前实现是否为高效的正确做法?
当前实现是正确但低效的。核心问题在于两次冗余的O(n)操作:
map {$_.Num}逐个转换元素类型,针对非num64类型的输入数组产生额外开销;CArray[num64].new()会再次遍历数组,将Raku容器值复制到C兼容的连续内存块中。
小规模数组下这些开销不明显,但数据量增大时,会完全抵消OpenMP并行带来的性能增益,甚至比原生Raku代码更慢。
2. 直接按数值类型布局内存的方法
有两种核心思路可避免类型转换和重复复制:
方法一:提前使用原生数值数组存储
直接创建num64类型的输入数组,跳过后续类型转换步骤:
# 直接初始化num64类型数组,而非字符串数组 my @a = num64(1.2), num64(2), num64(3.2), num64(10), num64(0.33); my @b = num64(2), num64(12), num64(18), num64(10), num64(8);
此时可省略map转换,直接用数组创建CArray,减少一次O(n)遍历。
方法二:直接访问Raku数组的底层内存
Raku的原生数组(如Array[num64])底层是连续内存布局,与C的double*兼容。可通过NativeCall直接获取内存地址,无需复制到CArray:
# 修改函数声明为接收指针类型 sub c_sum_two(num64 is rw, num64 is rw, num64 is rw, int32) is native(MYDYN) { * }; sub native_sum_two(Array[num64] @arr_one, Array[num64] @arr_two) { die "数组长度不一致" unless @arr_one.elems == @arr_two.elems; my $elems = @arr_one.elems; my $return = Array[num64].allocate($elems); # 直接传递数组的底层起始指针 c_sum_two(@arr_one[0], @arr_two[0], $return[0], $elems); $return }
利用Raku数组特性:第一个元素的指针即为整个数组的起始地址,直接传递给C的double*参数,完全避免数组复制开销。
3. 更优的数组返回方式
当前用CArray.allocate再转换为列表的方式存在两次内存分配,可优化为:
- 直接写入Raku预分配数组:如上述方法二中的
Array[num64].allocate($elems),直接传递其底层指针给C的third参数,C计算完成后Raku可直接使用该数组,无需额外转换; - 复用输入数组:若允许覆盖其中一个输入数组(如该数组后续不再使用),可直接将其指针作为
third参数传入,节省返回数组的内存分配开销。
内容的提问来源于stack exchange,提问作者Suman
相关产品推荐
相关产品推荐

