Contents

针对Coni原生Float32数组的硬核混沌测试

当您在浏览器中构建高性能应用程序时,JavaScript通用的 Number 类型通常是不够的。您需要原始的、连续的内存数组。在Coni WASM中,我们在计算最密集的应用程序上严重依赖原生的WebAssembly float32 数组。

Float32在Coni中到底用在哪里?

原生 float32 数组(make-float32-arrayf32-set!f32-get)是Coni性能层的绝对主干。您会发现它们驱动着:

  1. WebGL几何体: 在我们的 deep-focus-webgl 应用中,我们使用 float32 数组在原生层面通过数学方式塑造一个拥有80,000个顶点的大脑矩阵(240,000个浮点数!),然后通过 js/float32-buffer 将其一次性发送给GPU。
  2. 游戏引擎粒子系统: 对于像 flappy-birdneon-boids 这样的游戏,并行的 float32 数组无需垃圾回收的停顿,即可跟踪数以千计的X/Y坐标、速度和生命周期。
  3. 原始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 数组现在已经身经百战!