【问题标题】:SIMD in AssemblyScriptAssemblyScript 中的 SIMD
【发布时间】:2023-02-19 01:05:17
【问题描述】:

嘿,我在 AssemblyScript 中创建了一个 Box Blur 算法。

为了提高效率,我想使用 SIMD 操作。

例如我有它看起来像这样:

for(let column: i16 = x + deviationBase + 1; column < x + width - deviationBase; column++){
    r += load<u8>(rowPosition + (column + deviationBase) * 4    )
    g += load<u8>(rowPosition + (column + deviationBase) * 4 + 1) 
    b += load<u8>(rowPosition + (column + deviationBase) * 4 + 2)
    r -= load<u8>(rowPosition + (column - deviationBase) * 4    ) 
    g -= load<u8>(rowPosition + (column - deviationBase) * 4 + 1)
    b -= load<u8>(rowPosition + (column - deviationBase) * 4 + 2)
    
    store<u8>(rowPosition + column * 4    , (r / diameter) as u8)
    store<u8>(rowPosition + column * 4 + 1, (g / diameter) as u8)
    store<u8>(rowPosition + column * 4 + 2, (b / diameter) as u8)
  }

如何使用 SIMD 使这个 for 循环更快?如果你也能给我一些解释就好了:)

【问题讨论】:

    标签: webassembly simd assemblyscript


    【解决方案1】:

    我试图解决这个问题,我的尝试是here

    因此,处理 simd 的通常方法如下:AssemblyScript 也不例外:

    1. 将数据从常规寄存器或内存加载到 simd 寄存器中
    2. 计算矢量化计算
    3. 将数据存储回常规寄存器或内存

      AssemblyScript 有一种 simd 数据类型:v128,可以存储 16 个 u8。我们将使用它来进行 simd 计算。

      我使用这个resource 构建了一个框模糊示例。我做了一些决定,以删除边缘情况并只关注 simd 部分:

      1. 我使用了 rgba 格式,而不是 rgb 格式。将事物保持为 2 的幂有助于减少边缘情况,并且更适合 v128
      2. 我用值为 0 的像素填充要模糊的图像,以降低算法的复杂性。现在不需要边界检查。

        现在,对于实际的算法:我实现了三种算法。 box_blur_naive、box_blur_improved 和 box_blur_simd。

        在 box_blur_naive 中,有熟悉的加载和添加每个像素及其邻居。例如,对于红色:

        red += load<u8>(img + i * width * 4 + j * 4 + 0);
        red += load<u8>(img + (i - 1) * width * 4 + (j) * 4 + 0);
        red += load<u8>(img + (i + 1) * width * 4 + (j) * 4 + 0);
        red += load<u8>(img + (i) * width * 4 + (j - 1) * 4 + 0);
        red += load<u8>(img + (i) * width * 4 + (j + 1) * 4 + 0);
        

        基准时间:29.430s

        为了对此进行模拟,我们需要稍微重新安排一下。首先,垂直线 (i, i - 1, i + 1) 很容易转换为 simd 指令。问题是没有简单的方法来添加带有v128 的水平邻居,因为它们最终都在同一个寄存器中。

        幸运的是,在转置图像的帮助下,框模糊有一种简单的方法来分离水平和垂直添加,这就是 box_blur_improved 所做的:

        red += load<u8>(img + i * width * 4 + j * 4 + 0);
        red += load<u8>(img + (i - 1) * width * 4 + (j) * 4 + 0);
        red += load<u8>(img + (i + 1) * width * 4 + (j) * 4 + 0);
        ...
        red += load<u8>(transposed_img + (i - 1) * height * 4 + (j) * 4 + 0);
        red += load<u8>(transposed_img + (i + 1) * height * 4 + (j) * 4 + 0);
        

        基准测试时间:30.225s

        现在,我们只有垂直添加,所以我们终于可以开始引入v128。获取数据到v128

        line = v128.load(img + i * width * 4 + 4 + k * 16);
        line_before = v128.load(img + (i - 1) * width * 4 + 4 + k * 16);
        line_after = v128.load(img + (i + 1) * width * 4 + 4 + k * 16);
        

        这与之前的 loads 做同样的事情,但一次有 16 个 u8 值。我们现在可以执行添加:

        let result = v128.add<u8>(line, v128.add<u8>(line_before, line_after));
        

        此行执行之前的所有垂直添加。我们没有添加转置添加,因为我将很快解释未来的问题。

        v128.store(blurred_img + i * width * 4 + 4 + k * 16, result);
        

        这将结果存储在指定地址。仅此而已。

        基准测试时间:17.637s

        simd 解决方案似乎节省了大约一半的时间,并且代码没有完全 simdized。

        最后一个问题是,没有简单的方法可以使用v128(和一般的 simd)以简单的方式进行整数除法。

        考虑我们在第一个算法中将数据存储在内存中的方式:

        (load<u8>(blurred_img + j * width * 4 + i * 4 + 0) + red) / 5) as u8
        

        我们必须除以 5,这不是对 v128 的操作。可能有一些方法可以通过使用移位和减法来进行除法。从我们已经获得的速度提升来看,这样做或许是值得的。

    【讨论】:

      猜你喜欢
      • 2015-10-23
      • 2020-04-03
      • 2023-01-16
      • 2011-01-08
      • 2019-12-23
      • 1970-01-01
      • 1970-01-01
      • 2021-03-05
      • 2016-05-03
      相关资源
      最近更新 更多