You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

从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. 当前实现是否为高效的正确做法?
  2. 是否存在直接将数组按数值类型布局在内存中的方法?
  3. 有没有更优的数组返回方式?

问题解答

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 14:25:35