性能差异
D 比 F 慢的常见原因主要有几个:
数据宽度更大
F 是 32-bit float,D 是 64-bit double。即使都是硬件 FPU,D 的操作数、寄存器写回、load/store 都更宽。
在 RV32 上尤其明显,因为整数通用寄存器是 32-bit,但 double 是 64-bit。虽然浮点寄存器本身支持 64-bit,但地址计算、常量装载、checksum、部分 ABI 交互仍然更容易产生额外开销。
D 指令延迟通常高于 F 指令
很多 FPU 里 fadd.d/fmul.d/fdiv.d 的 latency 会比 fadd.s/fmul.s/fdiv.s 高,特别是 fdiv.d。你的 Whetstone kernel 里面有除法:
z = ( y / ( x + 2.0 ) ) + 0.25;所以如果双精度除法比单精度除法慢,就会直接反映到结果里。
双精度常量和访存压力更高
SP 常量是 4 字节,DP 常量是 8 字节。DP kernel 里会有更多 fld/fsd,常量池和访存带宽压力略高。
当前 benchmark 不完全是纯 FPU 吞吐测试
这个 Whetstone-style 测试混合了:
浮点加减乘除 数组访问 分支 函数调用/内联 整数循环控制 cycle 读取和打印之外的固定开销所以它测到的是“这段综合浮点 workload 的整体速度”,不是单独 fadd.s vs fadd.d 或 fmul.s vs fmul.d 的绝对吞吐。
这组数据反而有一个比较积极的结论:D 扩展只比 F 扩展慢 3.4%,说明 ICC500 的双精度实现对这个 workload 来说开销很小。
如果 RISC-V 只开 F 扩展,也就是只有单精度硬件浮点,编译器面对 double 通常有几种情况:
-march=rv32imf -mabi=ilp32f
float 会走硬件 F 指令,例如:
fadd.s fmul.s fdiv.s flw fsw但 double 没有 D 扩展可用,编译器会用软件浮点 helper 来模拟,例如:
__adddf3 __muldf3 __divdf3 __subdf3也就是说 double 计算仍然能跑,但会变成整数指令实现的软件模拟,性能会比硬件 D 慢很多。
-march=rv32imfd -mabi=ilp32d
这时 double 才会生成硬件双精度指令,例如:
fadd.d fmul.d fdiv.d fld fsd如果硬件实际没有 D,但软件误用 rv32imfd 编译
程序会生成 .d 指令。如果 core 不支持 D 扩展,执行到这些指令时应该触发 illegal instruction 异常。你的 core 如果异常处理不完整,就可能表现为卡死、跑飞或异常行为。
所以结论是:
仅 F 扩展可以运行 double 类型计算,但 double 会走软浮点模拟,不会走硬件 FPU 的双精度指令。
核心区别
RISC-V 的 F 扩展和 D 扩展都是浮点扩展,但支持的精度不同:
F = single-precision floating point D = double-precision floating point
也就是:
float -> F 扩展,32-bit 单精度 double -> D 扩展,64-bit 双精度
在指令层面:
F: fadd.s / fsub.s / fmul.s / fdiv.s / flw / fsw D: fadd.d / fsub.d / fmul.d / fdiv.d / fld / fsd
F 扩展
F 扩展提供硬件单精度浮点能力,主要服务于 C 语言里的 float。
如果编译参数是:
-march=rv32imf -mabi=ilp32f
那么 float 运算通常会生成硬件 F 指令:
fadd.s fmul.s fdiv.s flw fsw
此时如果代码里用了 double,编译器一般不会生成 .d 指令,因为硬件目标里没有 D 扩展。double 仍然可以计算,但会走软件模拟函数,例如:
__adddf3 __muldf3 __divdf3
所以仅有 F 扩展时:
float = 硬件计算,性能较好 double = 软件模拟,能跑但慢很多
D 扩展
D 扩展提供硬件双精度浮点能力,主要服务于 C 语言里的 double。
如果编译参数是:
-march=rv32imfd -mabi=ilp32d
那么 double 运算会生成硬件 D 指令:
fadd.d fmul.d fdiv.d fld fsd
同时 D 扩展依赖 F 扩展,所以通常写作:
rv32imfd
有 D 扩展时:
float = 硬件 F 指令 double = 硬件 D 指令
ABI 区别
-march 决定可以生成哪些指令,-mabi 决定函数调用约定、参数传递方式和浮点寄存器使用方式。
常见组合:
rv32im + ilp32 = 无硬件浮点 ABI rv32imf + ilp32f = 单精度硬件浮点 ABI rv32imfd + ilp32d = 双精度硬件浮点 ABI
需要注意:
ilp32d 需要 D 扩展 ilp32f 需要 F 扩展
如果硬件只有 F,却用:
-march=rv32imfd -mabi=ilp32d
编译器可能生成 fadd.d/fmul.d/fdiv.d。硬件不支持 D 时,执行这些指令会触发 illegal instruction 异常,系统可能卡死、跑飞或进入异常处理。
性能差异
一般来说,D 扩展比 F 扩展慢一些是正常的,因为:
double 数据宽度是 float 的 2 倍 D 指令的计算复杂度通常高于 F 指令 fdiv.d 往往比 fdiv.s 更慢 RV32 上处理 64-bit 数据会带来更多访存/常量装载/ABI 开销
对工程的建议
如果目标是测试 F 扩展性能,代码里应明确使用:
float 0.5f 1.0f
并反汇编确认出现:
fadd.s fmul.s fdiv.s flw fsw
如果目标是测试 D 扩展性能,代码里应明确使用:
double 0.5 1.0
并反汇编确认出现:
fadd.d fmul.d fdiv.d fld fsd