本节摘要:理解了标准史,再往下要问一句"凭什么"。现代 Fortran 的设计哲学可以压缩成三条主线:数组语义优先、抽象零开销、类型与接口强检查。三者共同回答一个问题——如何在让代码接近数学表达的同时,把性能决定权完整地交给编译器。本节用可运行代码把三条哲学落到地面,重点解释"默认无别名"这条假设为什么是 Fortran 敢于向量化的本钱。
同一个积分问题,两种实现:一种把循环写成三行索引操作,一种把循环写成整段数组表达式。前者在任何语言里都能写,后者只有 Fortran 敢保证性能不吃亏。为什么?因为 Fortran 在语言层面承诺了后者可以安全优化。这不是"风格偏好",是设计哲学的直接结果。
本节要讲清楚的就是:这三条哲学各自承诺了什么、代价是什么、以及你在写代码时该怎么顺着它们走。
Fortran 从设计之初就把数组当作自然的数据形态,而不是"指针加偏移"。物理场、网格、矩阵,在数学里是整体对象,在 Fortran 里也应该是整体对象。
program array_philosophy use iso_fortran_env, only: real64 implicit none real(real64) :: a(1000), b(1000), c(1000) integer :: i a = 1.0_real64 b = 2.0_real64 ! 整体数组操作:一句表达数学 c = a + b ! 等价的手写循环:F77 时代的写法,也是 C/Python 的常见写法 do i = 1, 1000 c(i) = a(i) + b(i) end do end program array_philosophy
c = a + b 与循环在语义上并不等价——前者向编译器宣告了"逐元素、无依赖",编译器可以并行化、向量化、重排内存访问;后者在 F77 时代编译器通常不敢这么做,因为循环体里也许藏着一个改变 a 的调用。这就是"数组优先"的实质:用语义换优化空间。你在写数组表达式时,其实是在给编译器签发并行许可证。
"零开销抽象"的意思是:语言提供的高级特性,不应该在运行时带来隐藏成本。数组切片、模块、过程重载,这些抽象在编译后都应该坍缩成与手写代码等价甚至更好的机器码。
实现这一点的关键机制是别名假设。Fortran 标准默认:两个不同的数组实参不会重叠指向同一块内存(除非显式声明等价)。编译器看到 subroutine add(a, b, c) 时,可以确信写 c 不会干扰读 a、b,于是放心重排指令、缓存寄存器、向量化。
subroutine saxpy(n, alpha, x, y, z) use iso_fortran_env, only: real64 implicit none integer, intent(in) :: n real(real64), intent(in) :: alpha real(real64), intent(in) :: x(:), y(:) real(real64), intent(out) :: z(:) z = alpha * x + y ! 编译器可安全向量化:x y z 默认互不重叠 end subroutine saxpy
对比 C 语言:C 的指针允许任意别名,编译器无法证明 z 与 x 不指向同一块内存,只能保守处理。这是为什么同样的算法,C 代码经常需要 restrict 关键字手动声明,而 Fortran 默认就是 restrict 的。代价是:如果你真的让两个数组重叠传进来,那是未定义行为,结果不可预测——所以现代代码里,宁可多拷贝一次,也别写有重叠嫌疑的调用。
科学计算里最贵的错误不是崩溃,而是"算完才发现结果不对"。F77 时代,一个实参类型不匹配会在运行时静默产生错误数值。现代 Fortran 用三层检查堵住这条路:implicit none 堵住隐式类型,显式接口(模块内定义的过程自带)堵住参数签名不匹配,intent 堵住"过程内部误改输入参数"。
module physics_mod use iso_fortran_env, only: real64 implicit none contains ! intent(in) 承诺不改动 x;编译器据此检查,也据此优化 pure function kinetic_energy(mass, velocity) result(energy) real(real64), intent(in) :: mass, velocity real(real64) :: energy energy = 0.5_real64 * mass * velocity * velocity end function kinetic_energy end module physics_mod program check_types use physics_mod, only: kinetic_energy use iso_fortran_env, only: real64 implicit none real(real64) :: m, v m = 1.0_real64 v = 2.0_real64 write(*,*) kinetic_energy(m, v) ! 输出 2.0 end program check_types
pure 关键字同时承诺"无副作用",这让函数可以在并行与向量化场景安全执行,也是把错误前置的手段——编译器会检查 pure 函数里是否偷偷修改了外部状态。三条检查叠加,错误从"运行时才暴露"提前到"编译期就报错",这正是科学软件能跑十年不出数值事故的原因之一。

这三条哲学不是教条,是给你判断依据。写代码时可以对照自查:能用整体数组表达就不要手写循环;能加 intent 就加,它既是文档也是检查;能用 pure 就声明,编译器会帮你验证副作用;绝不让两个数组实参重叠。反过来,当语言特性与性能冲突时——比如多态虚调用进了热循环——要知道该放弃的是抽象,不是性能,这是 Fortran 哲学里毫不含糊的部分。
阅读完本节,你应当能够:
implicit none、intent、kind 参数在强类型检查中各自扮演的角色光有哲学不够,还得有趁手的工具。下一节看编译器和 fpm 怎么把现代 Fortran 工程跑起来。