网站Logo 网友马大帅的博客

riscv 双精度扩展D与单精度扩展F的性能差异

ughostx
5
2026-04-17

性能差异


对比项

F 扩展

D 扩展

全称

Single-Precision Floating-Point

Double-Precision Floating-Point

主要支持类型

float

double

数据宽度

32-bit

64-bit

典型编译参数

-march=rv32imf -mabi=ilp32f

-march=rv32imfd -mabi=ilp32d

典型计算指令

fadd.s, fsub.s, fmul.s, fdiv.s

fadd.d, fsub.d, fmul.d, fdiv.d

典型访存指令

flw, fsw

fld, fsd

是否依赖其他浮点扩展

不依赖 D

依赖 F,通常写作 rv32imfd

仅有 F 时能否算 float

可以,走硬件 F 指令

不涉及

仅有 F 时能否算 double

可以,但通常走软件模拟

需要 D 扩展才走硬件双精度

没有 D 却执行 .d 指令

不会由 rv32imf 正常生成

会触发 illegal instruction 或异常行为

性能特点

通常更快,数据更小,指令延迟较低

通常略慢,数据更宽,计算复杂度更高

适合场景

控制算法、DSP、轻量 AI、图形/信号处理、一般嵌入式浮点

高精度数值计算、科学计算、需要更低舍入误差的算法

反汇编确认点

看是否有 .s 指令:fadd.s, fmul.s, fdiv.s

看是否有 .d 指令:fadd.d, fmul.d, fdiv.d

D 比 F 慢的常见原因主要有几个:

  1. 数据宽度更大

  2. F 是 32-bit float,D 是 64-bit double。即使都是硬件 FPU,D 的操作数、寄存器写回、load/store 都更宽。

  3. 在 RV32 上尤其明显,因为整数通用寄存器是 32-bit,但 double 是 64-bit。虽然浮点寄存器本身支持 64-bit,但地址计算、常量装载、checksum、部分 ABI 交互仍然更容易产生额外开销。

  4. D 指令延迟通常高于 F 指令

  5. 很多 FPU 里 fadd.d/fmul.d/fdiv.d 的 latency 会比 fadd.s/fmul.s/fdiv.s 高,特别是 fdiv.d。你的 Whetstone kernel 里面有除法:

  6. z = ( y / ( x + 2.0 ) ) + 0.25;

  7. 所以如果双精度除法比单精度除法慢,就会直接反映到结果里。

  8. 双精度常量和访存压力更高

  9. SP 常量是 4 字节,DP 常量是 8 字节。DP kernel 里会有更多 fld/fsd,常量池和访存带宽压力略高。

  10. 当前 benchmark 不完全是纯 FPU 吞吐测试

  11. 这个 Whetstone-style 测试混合了:

  12. 浮点加减乘除 数组访问 分支 函数调用/内联 整数循环控制 cycle 读取和打印之外的固定开销

  13. 所以它测到的是“这段综合浮点 workload 的整体速度”,不是单独 fadd.s vs fadd.d 或 fmul.s vs fmul.d 的绝对吞吐。

这组数据反而有一个比较积极的结论:D 扩展只比 F 扩展慢 3.4%,说明 ICC500 的双精度实现对这个 workload 来说开销很小。

如果 RISC-V 只开 F 扩展,也就是只有单精度硬件浮点,编译器面对 double 通常有几种情况:

  1. -march=rv32imf -mabi=ilp32f

  2. float 会走硬件 F 指令,例如:

  3. fadd.s fmul.s fdiv.s flw fsw

  4. 但 double 没有 D 扩展可用,编译器会用软件浮点 helper 来模拟,例如:

  5. __adddf3 __muldf3 __divdf3 __subdf3

  6. 也就是说 double 计算仍然能跑,但会变成整数指令实现的软件模拟,性能会比硬件 D 慢很多。

  7. -march=rv32imfd -mabi=ilp32d

  8. 这时 double 才会生成硬件双精度指令,例如:

  9. fadd.d fmul.d fdiv.d fld fsd

  10. 如果硬件实际没有 D,但软件误用 rv32imfd 编译

  11. 程序会生成 .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

动物装饰