针对Coni原生Float32数组的硬核混沌测试
Contents
当您在浏览器中构建高性能应用程序时,JavaScript通用的 Number 类型通常是不够的。您需要原始的、连续的内存数组。在Coni WASM中,我们在计算最密集的应用程序上严重依赖原生的WebAssembly float32 数组。
Float32在Coni中到底用在哪里?
原生 float32 数组(make-float32-array、f32-set!、f32-get)是Coni性能层的绝对主干。您会发现它们驱动着:
- WebGL几何体: 在我们的
deep-focus-webgl应用中,我们使用float32数组在原生层面通过数学方式塑造一个拥有80,000个顶点的大脑矩阵(240,000个浮点数!),然后通过js/float32-buffer将其一次性发送给GPU。 - 游戏引擎粒子系统: 对于像
flappy-bird或neon-boids这样的游戏,并行的float32数组无需垃圾回收的停顿,即可跟踪数以千计的X/Y坐标、速度和生命周期。 - 原始DSP音频: 我们的
sound-nodes合成器在将复杂的脉冲响应和噪声波形映射到WebAudio通道之前,使用float32数组在原生层面计算它们。
错误所在:类型强制转换与 f64.reinterpret_i64
因为这些数组极其关键,它们中的任何错误都将是灾难性的。最近,我们发现了一个问题:将一个精确的整数(如 150)传入 f32-set! 时,会莫名其妙地产生 0.0。
到底发生了什么?在较早版本的WASM编译器中,将整数传递给浮点数setter会导致编译器错误地使用 f64.reinterpret_i64 来解释这些位,从而完全扰乱了底层的值。
解决方案:Float32混沌测试 (Chaos Testing)
我们修补了编译器,以便在插入内存之前将整数正确地强制转换为浮点数,但我们需要一个保证,确保它永远不会再次退化。于是,混沌测试登场了。
在新的 float32_coercion_test.coni 测试套件中,我们构建了一个硬核的混沌测试。我们不是仅仅测试几个顺利通过的数字,而是分配了一个巨大的数组,并用确定性的、伪随机的循环来猛烈敲击它。
(deftest test-f32-chaos
"针对float32数组边界和类型转换的硬核混沌测试。"
(let [size 1000
arr (make-float32-array size)]
(loop [i 0]
(if (< i size)
(let [
;; 混合生成极端边缘用例:
val (if (= (mod i 4) 0) (- 0 i) ;; 负整数
(if (= (mod i 4) 1) (* i 10000) ;; 大正整数
(if (= (mod i 4) 2) (+ i 0.5) ;; 精确的float32小数
0))) ;; 零(整数)
]
(f32-set! arr i val)
(recur (+ i 1)))
nil))
;; ... 验证循环 ...
通过故意将极端正整数、负边界值、纯零和带有小数的浮点数这种残酷的混合物向 f32-set! 函数抛出1,000次,我们保证了能避免WASM陷阱(traps),并且类型强制转换变得坚不可摧。
如果您想以零开销构建GPU加速的应用程序或复杂的状态机,您需要对内存原语有绝对的信任。有了混沌测试,Coni的 float32 数组现在已经身经百战!