RVA 23 硬件遥遥无期,也许 rv64.zip 是面向 RISC-V 碎片化生态通用软件分发的一个选择

目前,RISC-V 软件分发由于碎片化的生态存在诸多问题:

  • rv64gc base 较弱,新的处理器实现了诸如 Bitmanip、Vector、Zicond 扩展提升了性能,但软件难以直接利用
  • 现有的带 Vector 的硬件在现有编译器(包括 GCC 15.0)开启 V 扩展后,在部分应用出现了显著的性能提升,但也在部分应用出现了显著的性能下降
  • 大家希望 RVA23 可以提供一个良好的平台,但在没有硬件的当下难以对其性能进行评估

经过观察我们发现,大多数程序中的性能热点函数较为集中,因此我们在函数粒度进行多版本的控制,在运行时根据目标机器的情况选择合适的版本,可以较好地解决这一问题。然而,现有的编译器基于 __attribute__((target_clones("default", "arch=+zba,+zbb", "arch=+b,+v,+zicond"))) 这样的函数多版本机制存在许多不好用的问题:

  • 参数与架构、编译器支持相关,影响代码在架构、编译器之间的可迁移性
  • 需要开发者手工实现函数多版本
  • 间接调用时容易引入额外开销

为此,我们为这 3 个问题提出了解决方法:

  • 为编译器添加新的参数 -ftarget-clones-table ,使得开发者可以在一个独立的文件中描述函数多版本,不影响代码的跨平台迁移问题,同时可以在一次写好多版本参数后,直接在打包过程中使用。
  • 自动化的函数克隆表的生成:我们提出了一个基于 perf 采样的方法来评估指定函数在不同扩展版本下的性能,使得函数克隆表可以自动生成,无需开发者人工干预。
  • 实现可兼容被调用函数的直接调用:在编译器中修改函数调用的逻辑,对于已经实现了多版本的函数,在与当前的函数调用者不存在 ISA 扩展不支持的问题时,可以像之前一样直接调用而无需访问 GOT 表,同时允许编译器像之前一样进行 inline 处理,解决了多版本调用中的开销问题。

在使用 GCC 15.0 + O3 参数在 rv64gcbv_zicond 真机上的实验数据表明,我们利用了大部分这些扩展可以加速的点,还在 SPECCPU 2006 的测试中,实现了比直接编译为 rv64gcbv_zicond 的二进制加速了 8.4%,因为我们避免了那些降低性能的扩展运行在我们的程序中,同时该二进制也保留了 rv64gc 平台的兼容性。

具体详情可以访问项目主页,也欢迎加入我们的交流群: https://rv64.zip

我们还将在 2025 RISC-V 中国峰会的软件与生态系统分论坛上分享我们的成果,欢迎 7月18日下午前来现场收听!

10 个赞

这个域名太有意思了.zip

1 个赞

读了介绍,有两个问题

  1. 如何基于 perf 较为精确的获取函数的性能表征?比如函数的不同参数组合可能会造成函数的内部执行路径不同->导致没有用到最好的函数版本
    目前想到的一个思路是用PGO(Profile-guided optimization),实际去采运行时数据来决定用哪个函数版本

我的理解是将最终函数地址的确定从运行时(传统函数派发)提前到了编译器链接时(后面提到了编译器inline)。因为目标平台支持的指令集集合不清楚,如何做到达到在目标平台运行时的最大性能?

请大佬不吝赐教:blush:

2 个赞

您说的不同的参数组合导致内部执行路径的情况确实存在,这个情况需要考虑 PGO 的数据输入生成,基于覆盖率反馈的方法解决,这一部分将作为 future work。目前的测试数据采用的是 SPECCPU 2006 的 Train 的 Data 作为输入, PGO 后在 Ref 的 Data 数据集上验证的性能结果。

这一部分编译器会自动生成一个 IFUNC Resolver 。在 Linux 上的实现是通过 hwprobe syscall 获取硬件支持的扩展以及 vendorid, archid, implid,然后选择一个合适的版本。

4 个赞

所以是在二进制程序运行时(至少是程序被OS加载到内存后)才决定调用的是哪个函数吗?(原谅我对 IFUNC Resolver 的概念不太理解)

2 个赞

是的,写入 GOT 表是运行时的过程。

2 个赞