[C#] 对24位图像进行水平翻转(FlipX)的跨平台SIMD硬件加速向量算法(使用YShuffleX3Kernel)


上一篇文章里,给大家讲解了32位图像水平翻转(FlipX)算法,于是本文来探讨更加复杂的24位图像水平翻转算法。
本文除了会给出标量算法外,还会给出向量算法。且这些算法是跨平台的,同一份源代码,能在 X86(Sse、Avx等指令集)及Arm(AdvSimd等指令集)等架构上运行,且均享有SIMD硬件加速。

一、标量算法

1.1 算法实现

标量算法对24位图像的处理,与32位图像非常相似,仅 cbPixel 的值不同。

源代码如下。

public static unsafe void ScalarDoBatch(byte* pSrc, int strideSrc, int width, int height, byte* pDst, int strideDst) {
   
    const int cbPixel = 3; // 24 bit: Bgr24, Rgb24.
    byte* pRow = pSrc;
    byte* qRow = pDst;
    for (int i = 0; i < height; i++) {
   
        byte* p = pRow + (width - 1) * cbPixel;
        byte* q = qRow;
        for (int j = 0; j < width; j++) {
   
            for (int k = 0; k < cbPixel; k++) {
   
                q[k] = p[k];
            }
            p -= cbPixel;
            q += cbPixel;
        }
        pRow += strideSrc;
        qRow += strideDst;
    }
}

1.2 基准测试代码

使用 BenchmarkDotNet 进行基准测试。

[Benchmark(Baseline = true)]
public void Scalar() {
   
    ScalarDo(_sourceBitmapData, _destinationBitmapData, false);
}

//[Benchmark]
public void ScalarParallel() {
   
    ScalarDo(_sourceBitmapData, _destinationBitmapData, true);
}

public static unsafe void ScalarDo(BitmapData src, BitmapData dst, bool useParallel = false) {
   
    int width = src.Width;
    int height = src.Height;
    int strideSrc = src.Stride;
    int strideDst = dst.Stride;
    byte* pSrc = (byte*)src.Scan0.ToPointer();
    byte* pDst = (byte*)dst.Scan0.ToPointer();
    bool allowParallel = useParallel && (height > 16) && (Environment.ProcessorCount > 1);
    if (allowParallel) {
   
        Parallel.For(0, height, i => {
   
            int start = i;
            int len = 1;
            byte* pSrc2 = pSrc + start * (long)strideSrc;
            byte* pDst2 = pDst + start * (long)strideDst;
            ScalarDoBatch(pSrc2, strideSrc, width, len, pDst2, strideDst);
        });
    } else {
   
        ScalarDoBatch(pSrc, strideSrc, width, height, pDst, strideDst);
    }
}

二、向量算法

2.1 算法思路

2.1.1 难点说明

24位像素的标量算法改的很简单,但是24位像素的向量算法要复杂的多。

这是因为向量大小一般是 16或32字节这样的2的整数幂,而24位像素是3个字节一组,无法整除。这就给地址计算、数据处理等方面,带来很大的难题。

2.1.2 解决办法:每次处理3个向量

既然1个向量无法被3整除,那么我们干脆用3个向量。这样肯定能被3整除。

例如使用Sse指令集时,向量大小为128位,即16个字节。3个向量,就是 48字节,正好能放下16个 24位像素。

随后面临一个难点——怎样对3个向量内的24位像素进行翻转?

根据前一篇文章的经验,处理1个向量内翻转时,可以使用Shuffle方法,只要构造好索引就行。现在面对3个向量,若有适用于3个向量的换位方法就好了。

为了解决这一难题,VectorTraits库提供了YShuffleX3等方法。且由于能确保索引总是在有效范围内,故还可以使用性能更好的 YShuffleX3Kernel 方法。

在大多数时候,YShuffleX3Kernel 是利用单向量的shuffle指令组合而成。由于 .NET 8.0 增加了一批“多向量换位”的硬件指令,于是在以下平台,能获得更好的硬件加速。

  • Arm: .NET 8.0 新增了对 AdvSimd指令集里的“2-4向量查表”指令的支持。例如 vqtbl3q_u8.
  • X86: .NET 8.0 新增了对 Avx512系列指令集的支持,而它提供了“2向量重排”的指令。例如 _mm_permutex2var_epi8.

详见 [C#] .NET8增加了Arm架构的多寄存器的查表函数(VectorTableLookup/VectorTableLookupExtension)

YShuffleX3 在 .NET Framework 等平台上运行时是没有硬件加速的,这是因为这些平台不支持Sse等向量指令。可以通过 Vectors 的 YShuffleX3Kernel_AcceleratedTypes 属性来得知哪些元素类型有硬件加速。当发现不支持时,宜切换为标量算法。

另外,还可以通过 Vectors.Instance.UsedInstructionSets 来查看该向量所使用的指令集。

2.1.3 用YShuffleX3Kernel对3个向量内的24位像素进行翻转

为了便于跨平台,这里使用了自动大小向量Vector。且由于它的大小不固定,于是需要写个循环来计算索引。根据上一篇文章的经验,我们可以在类的静态构造方法里做这个计算。

private static readonly Vector<byte</
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值