鲲鹏社区首页
中文
注册
开发者
Fortran软件使用KUPL接口

Fortran软件使用KUPL接口

高性能计算HPC

发表于 2025/12/23

0

Fortran软件如何使用KUPL接口

1 背景介绍

鲲鹏统一并行加速库(KUPL)是基于鲲鹏处理器深度优化的并行加速库,包括多线程编程,数据管理,矩阵编程三类基础与扩展功能,结合鲲鹏处理器硬件特性,提供优化的调度与同步算法、异步数据搬运等差异化能力,从高性能,高易用角度为鲲鹏平台应用加速提供助力。

详情链接:https://www.hikunpeng.com/developer/hpc/kupl

当前KUPL所有接口由C/C++、汇编语言实现,但在高性能计算领域,有许多软件是由Fortran语言编写的。

本文展示在Fortran软件中如何调用KUPL来进行优化。

2 基本流程

iso_c_binding 是 Fortran 2003 标准引入的一个内置模块,用于实现 Fortran 与 C 语言之间的互操作性。通过该模块,Fortran 程序可以安全、可移植地调用 C 函数,或被 C 程序调用。我们将使用该模块调用KUPL中的C/C++接口。

iso_c_binding 提供了一系列与 C 语言基本类型一一对应的 Fortran 类型种类(kind),例如:

C类型 Fortran对应
intinteger(c_int)
doublereal(c_double)
floatreal(c_float)
charcharacter(c_char)
size_tcharacter(c_char)
void*type(c_ptr)

步骤一:声明C兼容的Fortran接口

在Fortran中使用 bind(C) 属性声明 C 兼容的接口,并通过 iso_c_binding 提供的类型传递参数。

请注意:C 默认按值传递,Fortran 默认按引用传递。

因此,声明参数时:

      (1)如果 C 函数期望按值传参(如 int x),Fortran 声明的参数必须加 value 属性。如果 C 函数期望指针(如 int* x),Fortran 可直接声明。

      (2)如果C接口接收的是结构体,在Fortran中同样需要使用 bind(C) 定义同样的结构体。

步骤二:在Fortran中调用声明的接口

如上一步所说,Fortran 默认按引用传递,一般情况下,Fortran 调用时可直接传变量(默认传地址)。

一个比较常见的特殊情况是,如果C 函数参数是 void* 或 type(c_ptr),需要使用 c_loc 函数用于获取一个 Fortran 变量的 C 兼容指针(type(c_ptr))。

步骤三:编译链接

将C接口编译成.o文件,如果Fortran声明的接口是独立文件,也需要编译出.mod和.o文件,Fortran可执行文件链接时需要加上这两个.o文件。

3 KUPL接口转换举例

3.1 kupl_parallel_for

将kupl_parallel_for封装一层C接口以更方便在软件中使用:

int kupl_parallel_for(kupl_parallel_for_desc_t *desc, kupl_pf_func_t func, void *args); // KUPL定义的接口

// 为方便使用封装一层
void c_kupl_parallel_for(int lower, int upper, void (*func)(kupl_nd_range_t *, void *, int, int), void *args)
{
    kupl_nd_range_t range;
    KUPL_1D_RANGE_INIT(range, lower, upper, 1);
    kupl_parallel_for_desc_t desc = {
        .range = &range,
        .concurrency = g_threads,
        .egroup = g_egroup,
        .policy = KUPL_LOOP_POLICY_STATIC
    };
    kupl_parallel_for(&desc, func, args);
}

Fortran接口的声明如下:

module kupl_mod
  use iso_c_binding
  implicit none

  interface
    subroutine f_kupl_parallel_for(lower, upper, func, args) bind(c, name="c_kupl_parallel_for")
      import :: c_funptr, c_ptr, c_int
      integer(c_int), value :: lower
      integer(c_int), value :: upper
      type(c_funptr), value :: func
      type(c_ptr), value :: args
    end subroutine f_kupl_parallel_for
  end interface
end module kupl_mod

Fortran调用:

type(cell_loop_stru), target :: args ! args要用到c_loc,需要target属性
type(c_funptr) :: func ! parallel接口需要回调函数,声明为c_funptr

func = c_funloc(cell_loop) ! cell_loop是KUPL定义的回调函数,需要c_funloc转换

call f_kupl_parallel_for(1, n, func, c_loc(args)) ! 直接传变量,args因为是void*,需要c_loc

3.2 共享内存通信函数

KUPL提供了共享内存底层通信接口,以及基于这些接口的集合通信函数实现,常与MPI协作使用。

C接口:

int kupl_shm_comm_create(int size, int rank, int pid, kupl_shm_oob_cb_h oob_cbs, void *group, kupl_shm_comm_h *comm);

int kupl_shm_win_alloc(size_t size, kupl_shm_comm_h comm, void **baseptr, kupl_shm_win_h *win);

void c_kupl_shm_comm_create(int *shm_comm, int rank, kupl_shm_comm_h *kupl_comm)
{
    MPI_Comm comm = MPI_Comm_f2c(*shm_comm); // MPI提供通信域的转换接口

    int size;
    MPI_Comm_size(comm, &size);
    // ...一些赋值...

    kupl_shm_comm_create(size, rank, pid, oob_cbs_h, (void *)comm, kupl_comm);
}

Fortran接口:

! 接口用到了结构体需要定义
type, bind(c) :: kupl_shm_comm_h
  type(c_ptr) :: ptr
end type kupl_shm_comm_h

type, bind(c) :: kupl_shm_win_h
  type(c_ptr) :: ptr
end type

subroutine f_kupl_shm_comm_create(comm, rank, kupl_comm) bind(c, name="c_kupl_shm_comm_create")
  import :: c_int, kupl_shm_comm_h
  integer(c_int) :: comm
  integer(c_int), value :: rank
  type(kupl_shm_comm_h), intent(out) :: kupl_comm
end subroutine

function f_kupl_shm_win_alloc(buf_size, kupl_comm, baseptr, win) bind(c, name="kupl_shm_win_alloc") ! function定义需要返回值的接口
  import :: c_size_t, c_ptr, kupl_shm_comm_h, kupl_shm_win_h, c_int
  integer(c_size_t), value :: buf_size
  type(kupl_shm_comm_h), value :: kupl_comm
  type(c_ptr), intent(out) :: baseptr
  type(kupl_shm_win_h), intent(out) :: win
  integer(c_int) :: err ! 最后一个参数是函数返回值
end function

Fortran调用:

type(kupl_shm_comm_h) :: kupl_comm
call f_kupl_shm_comm_create(mpi_comp_comm, mpi_comp_rank, kupl_comm)

type(c_ptr) :: local_buffer
type(kupl_shm_win_h) :: kupl_win
real(8), pointer :: mpi_send_buf(:)
err = f_kupl_shm_win_alloc(buf_size, kupl_comm, local_buffer, kupl_win)
CALL c_f_pointer(local_buffer, mpi_send_buf, [send_count]) ! local_buffer是出参,Fortran需要c_f_pointer转为Fortran指针

4 注意事项

1、C 函数需要的入参是值,Fortran 的参数传递方式一定要加 value,否则会读错内存。

2、传给C接口的字符串必须以 \0 结尾:

msg = 'Hello' // c_null_char ! 必须补 \0

3、在 Fortran 中定义的二维(及以上)数组传递给 C 函数时,在内存中的排布方式(存储顺序)是列优先,而 C 语言默认使用行优先。因此将Fortran的数组传给C前,需要先将Fortran的数组按照行优先的逻辑重新排列再传入。

4、C 数组下标从 0 开始,Fortran 从 1 开始。在内存中传递的只是地址,与下标无关,但如果涉及到索引计算等操作时,需注意下标代表的含义。


本页内容