核心思路是泛型机制负责类型路由、条件编译负责硬件裁剪,二者分层正交:泛型宏统一接口,条件编译在各架构实现中选择最优路径,确保签名一致、编译期静态确定、安全可移植。

核心思路是用泛型机制做类型路由,用条件编译做硬件特征裁剪,两者分层协作:泛型处理“数据类型维度”,条件编译处理“硬件能力维度”,不混在同一层判断。
分层解耦:泛型负责类型,条件编译负责架构
泛型(如 C17 的 _Generic)在编译期根据参数类型选择函数,不感知硬件;条件编译(如 #ifdef __riscv、#ifdef __aarch64__)则根据目标平台启用/屏蔽整段实现。二者正交使用,避免嵌套宏地狱。
- 泛型宏只写一次,例如
#define vec_add(a, b, n) _Generic((a), float*: riscv_vec_add_f32, int32_t*: riscv_vec_add_i32)(a,b,n) - 而
riscv_vec_add_f32内部再用#ifdef __riscv_vector判断是否启用 V 扩展,或 fallback 到标量循环 - 同名函数在不同架构下可分别实现:
arm_vec_add_f32用 NEON 指令,x86_vec_add_f32用 AVX,头文件统一声明,链接时由构建系统选入
统一接口 + 架构特化实现
对外暴露一个泛型宏或内联函数,内部通过条件编译加载对应架构的最优实现。关键在于所有架构下的函数签名必须严格一致(参数类型、顺序、返回值),否则泛型分支无法对齐。
- 定义公共头文件
algo.h,只声明void algo_filter(float* in, float* out, size_t len); - 在
algo_riscv.c中实现该函数,开头加#if defined(__riscv) && __riscv_xlen == 64 && defined(__riscv_vector) - 在
algo_arm.c中同样实现,用#ifdef __ARM_FEATURE_NEON控制向量化路径 - 构建时由 CMake 或 Makefile 根据
$ARCH变量决定编译哪个源文件
安全切换的关键控制点
所谓“安全”,不是运行时动态跳转,而是编译期静态确定唯一路径,杜绝歧义和未定义行为。
- 每个泛型分支必须有明确类型覆盖,避免依赖
default:——它可能掩盖类型误用 - 条件编译中优先使用工具链预定义宏(如
__riscv_vector,__ARM_ARCH_8A),而非自定义宏,确保与编译器实际行为一致 - 对关键路径加入
_Static_assert,例如验证sizeof(int)是否符合目标 ABI,防止跨架构移植时隐式错误 - 禁用浮点泛型分支在 soft-float 工具链下生效:用
#if !defined(__riscv_soft_float)包裹float和double分支
实操建议:从算子库起步
不必一开始就覆盖全部算法,先聚焦高频、性能敏感的原子算子(如向量加、矩阵乘、激活函数),用泛型+条件编译封装好,再逐层组合。
- 第一步:为
relu(float* x, size_t n)实现三套后端(RISC-V V 扩展 / ARM NEON / x86 SSE) - 第二步:用
_Generic封装成ALGO_RELU(x, n),支持float*、int8_t*等输入类型 - 第三步:在顶层应用中只调用
ALGO_RELU,编译时自动绑定对应架构最优实现,无需修改业务代码










