在 N 皇后和矩阵乘法上对 20 种编程语言进行基准测试
对 20 种编程语言在 N 皇后和矩阵乘法上的基准测试,引发了关于这类测试究竟衡量什么的争论:原始语言/运行时速度、编译器质量,还是 NumPy 和 BLAS 等生态系统的实力。评论者指出,朴素的、无需库的实现会让 Python 和其他动态语言看起来比 C、Julia 或 Nim 慢得多,并质疑 JIT 预热、内存布局以及非惯用代码是否会扭曲结果。许多人认为,这些微基准虽然有助于理解底层性能特征,但对典型真实工作流所能说明的很少;在真实工作流中,优化库、求解器和领域专用工具才是主导。
基准测试目标与方法
- 基准测试涵盖 N 皇后、矩阵乘法、数独和床铺覆盖,主要使用手写的、“朴素”的实现,往往受 Rosetta 风格示例启发。
- 一些人认为,这对于衡量当你必须自己实现一个新算法时的语言/VM 基线性能很有用。
- 另一些人则认为,这些代码在若干语言中并不理想且不符合惯用法,因此结果可能更多反映的是特定实现,而不是语言本身。
- 是否应包含 JIT 预热和启动时间存在争议;有人认为“从 CLI 到结果”更贴近现实,也有人希望使用已预热的 JIT,或对 AOT 语言把编译时间也算进去。
库 vs “纯语言”性能
- 一个主要讨论点是反对排除 NumPy、BLAS 或专用张量库等库。
- 一方观点:基准测试应在不借助外部 C/Fortran 库的情况下比较语言;否则你测到的只是 FFI 开销。
- 另一方观点:惯用的数值 Python、C# 等本来就会使用这类库;纯语言矩阵乘法不现实,也会误导对真实世界性能的判断。
- 有人指出,几乎所有语言中的高性能矩阵乘法最终都依赖高度优化的库,通常还包含汇编,而 Python 通常只有在调用这些库时才算“快”。
矩阵乘法的难度与 BLAS
- 多条评论强调,真正达到 BLAS 水平的矩阵乘法需要仔细的分块、SIMD、面向缓存/寄存器的内核,有时甚至需要汇编;编译器不可能凭一个简单的三重循环就达到峰值的 90% 以上。
- 有人声称,借助 C++/Nim 级别的控制,你可以在特定形状/稀疏模式下匹配甚至超过 BLAS;也有人反驳,报告的实验中,手写代码除非付出大量努力,否则远远落后于 OpenBLAS。
- 有人提到一个 Nim 项目,其基准测试显示在多种 CPU 上性能可与 OpenBLAS 和 MKL 相当,使用了生成的 SIMD 微内核和自定义线程调度。
语言特定观察
- Rust:最初的矩阵乘法因
Vec<Vec>布局和边界检查而较慢;现在基于迭代器或静态大小的实现已能与 C 持平。 - C#:早期矩阵乘法使用矩形数组(每次访问都有额外乘法)。改为数组的数组后性能更接近 Java;进一步提升预计来自 SIMD(
Vector<T>/System.Numerics.Tensors)。 - Swift:优化后矩阵乘法可以接近 C/Rust,但数独仍然很慢,因为有大量堆分配且缺少静态数组。
- Julia:最初的矩阵乘法使用了错误的内存方向并禁用了 SIMD;修正后的代码性能接近 C。
- Mojo:矩阵乘法约比 C 慢 2 倍,一些人认为考虑到易用性这可以接受,但另一些人觉得与 Julia 相比它过于冗长。
JIT、启动时间与硬件
- 一些人认为 JIT 语言在冷启动运行中处于劣势;另一些人反驳说,很多现实用途(CLI、构建)都是短命的,因此冷启动性能很重要。
- ARM big.LITTLE 系统引发了一个问题:基准测试到底是在性能核还是效率核上运行。
- 单线程、多线程和向量化细节(例如 OpenBLAS 线程、AVX/AVX-512 使用、边界检查)会显著影响观察到的差异。
图表与替代指标
- 堆叠柱状图会掩盖比较,因为少数语言(PHP、Ruby、Perl、纯 CPython)极其缓慢;有人建议使用单独图表、对数刻度或 ops/sec。
- 有些人希望增加诸如 gzipped 源码大小或 LOC 之类的指标,以反映表达力和代码复杂度;也有人指出,要定义一个公平、以人为中心的大小指标很困难。
- 多位评论者提到了更大的基准套件(例如 Computer Language Benchmarks Game),并要求加入更多贴近现实的任务(文件 I/O、JSON 解析、服务器)。