您的位置:首页 > 财经 > 金融 > 企业网站策划书范文3000字_百度批量查询工具_开封网络推广哪家好_网页设计代码

企业网站策划书范文3000字_百度批量查询工具_开封网络推广哪家好_网页设计代码

2025/3/17 17:57:34 来源:https://blog.csdn.net/zyl910/article/details/146298725  浏览:    关键词:企业网站策划书范文3000字_百度批量查询工具_开封网络推广哪家好_网页设计代码
企业网站策划书范文3000字_百度批量查询工具_开封网络推广哪家好_网页设计代码

文章目录

  • 一、算法思路
    • 1.1 瓶颈分析
    • 1.2 优化思路
    • 1.3 计算索引
  • 二、算法实现
    • 2.1 程序里计算索引
    • 2.2 思路A的实现
    • 2.3 思路B的实现
  • 三、基准测试结果
    • 3.1 X86 架构
      • 3.1.1 X86 架构上`.NET 6.0`程序的测试结果
      • 3.1.2 X86 架构上`.NET 7.0`程序的测试结果
      • 3.1.3 X86 架构上`.NET 8.0`程序的测试结果
    • 3.2 Arm 架构
      • 3.2.1 Arm 架构上`.NET 6.0`程序的测试结果
      • 3.2.2 Arm 架构上`.NET 7.0`程序的测试结果
      • 3.2.3 Arm 架构上`.NET 8.0`程序的测试结果
    • 3.3 .NET Framework
  • 四、结语
  • 附录

在本主题的 上一篇文章里,给大家讲解了24位图像水平翻转(FlipX)算法。但该文章主要是为了介绍 YShuffleX3Kernel 的使用,该算法性能并不是最优的。于是本文将介绍如何使用 YShuffleX2Kernel 来优化程序。而且Imageshop在留言区给了一份C语言的、基于Sse系列指令集实现的代码,正好一起对比一下。

一、算法思路

1.1 瓶颈分析

当硬件没有多向量换位(shuffle)的指令时,一般来说需要3条换位指令才能实现 YShuffleX3Kernel 方法。

对于24位图像水平翻转来说,每次内循环是处理3个向量长度的数据,需调用3次 YShuffleX3Kernel 方法,故共调用了 3*3=9 条换位指令。

且在使用X86平台的Avx2指令集时,因它没有提供“跨小道(lane)换位”指令,导致需要用2条换位指令来实现“向量内全范围的换位”。于是对于24位图像水平翻转来说,故共调用了 3*3*2=18 条换位指令。这个数量很大了。

于是,若能降低换位指令的数量,将能提升程序的性能。

1.2 优化思路

X86架构的Sse指令集,与Arm架构的AdvSimd指令集里均使用128位的向量寄存器,即16个字节。

24位像素是3个字节一组。对于16个字节向量寄存器来说,仅能完整存放 5个像素((int)(16 / 3) = 5),占据了 3*5=15 个字节。剩余的1个字节超出向量大小的边界了,位于另一个向量中。

换个角度来看,使用1条单向量的换位指令,能将16个字节中的15个字节做好水平翻转。仅剩1个字节的数据不正确,此时可以另想办法来修正。

一种思路是用标量的内存读写语句来修正这些不正确的字节。这就是 Imageshop的Sse版算法的思路,对于每次处理48个字节的内循环,使用标量的内存读写语句修正了6个字节的数据。这种办法能将换位指令数量降到了最低,但代价是增加了标量的内存读写工作。需要进行基准测试,实际对比性能。

另一种思路是再用1条换位指令来计算剩余字节的数据,然后使用条件掩码将这2种结果(15个字节+剩余1字节)合并。而且大多数架构(X86、Arm)的换位指令带有清零能力,小心的调整掩码,能使无效字节为0,这样仅需 or 指令就能将2种结果合并了。

上面这种计算,正好是“2向量的换位”操作。X86架构的Avx512系列指令集,以及Arm架构的AdvSimd指令集,均提供了这样的指令。而且 .NET 8.0 支持了这些指令的内在函数(Intrinsic Functions)。

手动调用内在函数是很繁琐的,且难以跨平台。于是 VectorTraits 提供了 YShuffleX2Kernel 方法。它会检查硬件是否支持“2向量的换位”指令,如支持便会使用各平台的这些指令;若不支持,便会自动计算好掩码,调用2条换位指令并组合。

也就是说,当硬件没有多向量换位的指令时,YShuffleX2Kernel 比起 YShuffleX3Kernel,能少使用1个换位指令。从而降低了开销,优化了性能。

1.3 计算索引

对于24位图像水平翻转来说,每次内循环是处理3个向量长度的数据,此时需要3个索引向量。其中 索引0、索引2,仅需访问2个数据向量,正好能使用 YShuffleX2Kernel。而中间的索引1,默认情况会访问3个数据向量。下面的表格详细说明了这些情况。

Nameoffset01234567891011121314151617181920212223242526272829303132333435363738394041424344454647
Indice045464742434439404136373833343530313227282924252621222318192015161712131491011678345012
Indice01629303126272823242520212217181914
Indice1E1615161112138910567234-10
Indice1A031322728292425262122231819201516
Indice1B1516171213149101167834501
Indice201712131491011678345012

变量说明:

  • Indice: YShuffleX3Kernel 的索引。3个索引均写在同一行。
  • Indice0: YShuffleX2Kernel 的索引0。
  • Indice1E: 演示了错误方案下的索引1。
  • Indice1A: 索引1使用A方案。
  • Indice1B: 索引1使用B方案。
  • Indice2: YShuffleX2Kernel 的索引2。

索引2(Indice2)最简单,直接使用 YShuffleX3Kernel的索引2就行。因它仅需访问第0个与第1个输入向量。

索引0(Indice2)稍微复杂一点。它仅需访问第1个与第2个输入向量,于是从数据地址的偏移量(offset)来说,偏移量为16。即向量寄存器的字节数(Vector<byte>.Count)。

索引1最麻烦,因为它需要访问3个向量。于是上表给出了 Indice1E 这一行,将 offset 设为16,于是可以观察到它不仅有小于下界的值(-1),还有超过上界的值(16)。有2种思路来解决这一难题:

  • A. 干脆用 YShuffleX3Kernel 来计算它。于是可以直接使用 YShuffleX3Kernel 时的索引1,即 Indice1A。也就说,内循环会调用2次 YShuffleX2Kernel,和1次 YShuffleX3Kernel,比起调用3次YShuffleX3Kernel的开销低。
  • B. 干脆为它提供的输入向量。此时用“偏移量15”来加载2笔连续的向量数据,便能用 YShuffleX2Kernel 来计算了。由于现在数据加载地址很近(偏移15与偏移16很近),且现在处理器的高速缓存(Cache)技术很成熟,使得这种加载的开销很低。

随后可以通过基准测试,来看哪种思路的性能更好。

二、算法实现

2.1 程序里计算索引

首先需要计算索引。可以在先前YShuffleX3Kernel的索引计算代码上修改,关键是处理好 offset。

// -- Indices of YShuffleX3Kernel
private static readonly Vector<byte> _shuffleIndices0;
private static readonly Vector<byte> _shuffleIndices1;
private static readonly Vector<byte> _shuffleIndices2;// -- Indices of YShuffleX2Kernel
private static readonly byte _shuffleX2Offset0 = (byte)Vector<byte>.Count;
private static readonly byte _shuffleX2Offset1A = 0;
private static readonly byte _shuffleX2Offset1B = (byte)(Vector<byte>.Count / 3 * 3);
private static readonly byte _shuffleX2Offset2 = 0;
private static readonly Vector<byte> _shuffleX2Indices0;
private static readonly Vector<byte> _shuffleX2Indices1A; // Need YShuffleX3Kernel
private static readonly Vector<byte> _shuffleX2Indices1B;
private static readonly Vector<byte> _shuffleX2Indices2;static ImageFlipXOn24bitBenchmark() {const int cbPixel = 3; // 24 bit: Bgr24, Rgb24.int vectorWidth = Vector<byte>.Count;int blockSize = vectorWidth * cbPixel;Span<byte> buf = stackalloc byte[blockSize];for (int i = 0; i < blockSize; i++) {int m = i / cbPixel;int n = i % cbPixel;buf[i] = (byte)((vectorWidth - 1 - m) * cbPixel + n);}_shuffleIndices0 = Vectors.Create(buf);_shuffleIndices1 = Vectors.Create(buf.Slice(vectorWidth * 1));_shuffleIndices2 = Vectors.Create(buf.Slice(vectorWidth * 2));// -- Indices of YShuffleX2Kernel_shuffleX2Indices0 = Vector.Subtract(_shuffleIndices0, new Vector<byte>(_shuffleX2Offset0));_shuffleX2Indices1A = _shuffleIndices1; // _shuffleX2Offset1A is 0_shuffleX2Indices1B = Vector.Subtract(_shuffleIndices1, new Vector<byte>(_shuffleX2Offset1B));_shuffleX2Indices2 = _shuffleIndices2; // _shuffleX2Offset2 is 0
}

可见,代码改动不多。仅需使用 Vector.Subtract 做向量减法,减去偏移量。

2.2 思路A的实现

根据上面的思路A,编写代码。源代码如下。

public static unsafe void UseVectorsX2AArgsDoBatch(byte* pSrc, int strideSrc, int width, int height, byte* pDst, int strideDst) {const int cbPixel = 3; // 24 bit: Bgr24, Rgb24.Vectors.YShuffleX2Kernel_Args(_shuffleX2Indices0, out var indices0arg0, out var indices0arg1, out var indices0arg2, out var indices0arg3);Vectors.YShuffleX3Kernel_Args(_shuffleX2Indices1A, out var indices1arg0, out var indices1arg1, out var indices1arg2, out var indices1arg3);Vectors.YShuffleX2Kernel_Args(_shuffleX2Indices2, out var indices2arg0, out var indices2arg1, out var indices2arg2, out var indices2arg3);int vectorWidth = Vector<byte>.Count;if (width <= vectorWidth) {ScalarDoBatch(pSrc, strideSrc, width, height, pDst, strideDst);return;}int maxX = width - vectorWidth;byte* pRow = pSrc;byte* qRow = pDst;for (int i = 0; i < height; i++) {Vector<byte>* pLast = (Vector<byte>*)pRow;Vector<byte>* qLast = (Vector<byte>*)(qRow + maxX * cbPixel);Vector<byte>* p = (Vector<byte>*)(pRow + maxX * cbPixel);Vector<byte>* q = (Vector<byte>*)qRow;for (; ; ) {Vector<byte> data0, data1, data2, temp0, temp1, temp2;// Load.data0 = p[0];data1 = p[1];data2 = p[2];// FlipX.temp0 = Vectors.YShuffleX2Kernel_Core(data1, data2, indices0arg0, indices0arg1, indices0arg2, indices0arg3);temp1 = Vectors.YShuffleX3Kernel_Core(data0, data1, data2, indices1arg0, indices1arg1, indices1arg2, indices1arg3);temp2 = Vectors.YShuffleX2Kernel_Core(data0, data1, indices2arg0, indices2arg1, indices2arg2, indices2arg3);// Store.q[0] = temp0;q[1] = temp1;q[2] = temp2;// Next.if (p <= pLast) break;p -= cbPixel;q += cbPixel;if (p < pLast) p = pLast; // The last block is also use vector.if (q > qLast) q = qLast;}pRow += strideSrc;qRow += strideDst;}
}

前面文章有提到,使用 Args、Core后缀的方法可以将部分运算从循环内,挪至循环前,从而提高了性能。于是本函数便使用了这个办法。

2.3 思路B的实现

根据上面的思路B,编写代码。源代码如下。

public static unsafe void UseVectorsX2BArgsDoBatch(byte* pSrc, int strideSrc, int width, int height, byte* pDst, int strideDst) {const int cbPixel = 3; // 24 bit: Bgr24, Rgb24.int offsetB0 = _shuffleX2Offset1B;int offsetB1 = offsetB0 + Vector<byte>.Count;Vectors.YShuffleX2Kernel_Args(_shuffleX2Indices0, out var indices0arg0, out var indices0arg1, out var indices0arg2, out var indices0arg3);Vectors.YShuffleX2Kernel_Args(_shuffleX2Indices1B, out var indices1arg0, out var indices1arg1, out var indices1arg2, out var indices1arg3);Vectors.YShuffleX2Kernel_Args(_shuffleX2Indices2, out var indices2arg0, out var indices2arg1, out var indices2arg2, out var indices2arg3);int vectorWidth = Vector<byte>.Count;if (width <= vectorWidth) {ScalarDoBatch(pSrc, strideSrc, width, height, pDst, strideDst);return;}int maxX = width - vectorWidth;byte* pRow = pSrc;byte* qRow = pDst;for (int i = 0; i < height; i++) {Vector<byte>* pLast = (Vector<byte>*)pRow;Vector<byte>* qLast = (Vector<byte>*)(qRow + maxX * cbPixel);Vector<byte>* p = (Vector<byte>*)(pRow + maxX * cbPixel);Vector<byte>* q = (Vector<byte>*)qRow;for (; ; ) {Vector<byte> data0, data1, data2, dataB0, dataB1, temp0, temp1, temp2;// Load.data0 = p[0];data1 = p[1];data2 = p[2];dataB0 = *(Vector<byte>*)((byte*)p + offsetB0);dataB1 = *(Vector<byte>*)((byte*)p + offsetB1);// FlipX.temp0 = Vectors.YShuffleX2Kernel_Core(data1, data2, indices0arg0, indices0arg1, indices0arg2, indices0arg3);temp1 = Vectors.YShuffleX2Kernel_Core(dataB0, dataB1, indices1arg0, indices1arg1, indices1arg2, indices1arg3);temp2 = Vectors.YShuffleX2Kernel_Core(data0, data1, indices2arg0, indices2arg1, indices2arg2, indices2arg3);// Store.q[0] = temp0;q[1] = temp1;q[2] = temp2;// Next.if (p <= pLast) break;p -= cbPixel;q += cbPixel;if (p < pLast) p = pLast; // The last block is also use vector.if (q > qLast) q = qLast;}pRow += strideSrc;qRow += strideDst;}
}

思路B的内循环,全部使用 YShuffleX2Kernel_Core 来计算。只是需要为索引1(_shuffleX2Indices1B),加载其特有偏移量的2个向量(dataB0、dataB1)。由于偏移量是基于字节的,于是在在计算地址时,需将指针p转为了 byte*类型后再加偏移量,最后再转回 Vector<byte>* 类型来做向量加载。

三、基准测试结果

3.1 X86 架构

3.1.1 X86 架构上.NET 6.0程序的测试结果

X86架构上.NET 6.0程序的基准测试结果如下。

BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.3476)
AMD Ryzen 7 7840H w/ Radeon 780M Graphics, 1 CPU, 16 logical and 8 physical cores
.NET SDK 9.0.200[Host]     : .NET 6.0.36 (6.0.3624.51421), X64 RyuJIT AVX2DefaultJob : .NET 6.0.36 (6.0.3624.51421), X64 RyuJIT AVX2| Method            | Width | Mean        | Error     | StdDev    | Ratio | Code Size |
|------------------ |------ |------------:|----------:|----------:|------:|----------:|
| Scalar            | 1024  |  1,047.8 us |  10.47 us |   9.79 us |  1.00 |   2,053 B |
| UseVectors        | 1024  |    375.6 us |   7.49 us |   7.69 us |  0.36 |   4,505 B |
| UseVectorsArgs    | 1024  |    202.0 us |   4.02 us |   4.94 us |  0.19 |   4,234 B |
| UseVectorsX2AArgs | 1024  |    149.6 us |   2.97 us |   8.63 us |  0.14 |   4,275 B |
| UseVectorsX2BArgs | 1024  |    125.2 us |   2.39 us |   2.11 us |  0.12 |   3,835 B |
| ImageshopSse      | 1024  |    145.0 us |   2.81 us |   4.30 us |  0.14 |   1,440 B |
|                   |       |             |           |           |       |           |
| Scalar            | 2048  |  4,248.4 us |  41.26 us |  38.59 us |  1.00 |   2,053 B |
| UseVectors        | 2048  |  2,578.7 us |  18.84 us |  17.63 us |  0.61 |   4,505 B |
| UseVectorsArgs    | 2048  |  2,022.4 us |  22.92 us |  21.44 us |  0.48 |   4,234 B |
| UseVectorsX2AArgs | 2048  |  1,710.7 us |  16.22 us |  14.38 us |  0.40 |   4,275 B |
| UseVectorsX2BArgs | 2048  |  1,682.1 us |  18.11 us |  16.94 us |  0.40 |   3,835 B |
| ImageshopSse      | 2048  |  1,854.0 us |  21.15 us |  19.78 us |  0.44 |   1,440 B |
|                   |       |             |           |           |       |           |
| Scalar            | 4096  | 16,231.0 us | 133.81 us | 118.62 us |  1.00 |   2,053 B |
| UseVectors        | 4096  |  8,418.7 us |  55.64 us |  52.04 us |  0.52 |   4,490 B |
| UseVectorsArgs    | 4096  |  5,906.4 us |  49.55 us |  46.34 us |  0.36 |   4,219 B |
| UseVectorsX2AArgs | 4096  |  5,497.9 us |  46.65 us |  43.64 us |  0.34 |   4,260 B |
| UseVectorsX2BArgs | 4096  |  5,385.9 us |  79.28 us |  74.16 us |  0.33 |   3,820 B |
| ImageshopSse      | 4096  |  5,784.4 us |  50.70 us |  44.94 us |  0.36 |   1,440 B |
  • Scalar: 标量算法。
  • UseVectors: 向量算法。
  • UseVectorsArgs: 使用Args将部分运算挪至循环前的向量算法。
  • UseVectorsX2AArgs: 思路A的算法(2次YShuffleX2Kernel + 1次YShuffleX3Kernel)。
  • UseVectorsX2BArgs: 思路B的算法(3次YShuffleX2Kernel,并处理特殊偏移 )。
  • ImageshopSse: Imageshop的Sse版算法,翻译为 C# 语言。

以1024时的测试结果为例,来观察向量化算法比起标量算法的性能提升。

  • UseVectors:1,047.8/492.3 ≈ 2.13。即性能提升了 2.13 倍。
  • UseVectorsArgs:1,047.8/202.0 ≈5.19。即性能提升了5.19 倍。
  • UseVectorsX2AArgs:1,047.8/149.6 ≈7.00。即性能提升了 7.00 倍。
  • UseVectorsX2BArgs:1,047.8/125.2 ≈8.37。即性能提升了 8.37 倍。
  • ImageshopSse:1,047.8/145.0 ≈7.23。即性能提升了 7.23 倍。

可以发现,减少换位指令数量的办法确实有效,UseVectorsX2AArgs 等函数的速度,均比 UseVectorsArgs 要快。

还可发现,内循环仅使用3次换位的ImageshopSse ,性能与 UseVectorsX2AArgs 差不多。由于AVX2没有“跨小道(lane)换位”指令,导致需要用2条换位指令来实现“向量内全范围的换位”,于是UseVectorsX2AArgs的内循环里有 (2+3+2) * 2 = 14 条换位指令。虽然换位指令的数量相差这么多,但性能差不多,说明标量内存读写的开销颇大。

UseVectorsX2BArgs 的速度最快。这是因为 UseVectorsX2BArgs的内循环 比 UseVectorsX2AArgs 少一次换位操作,代价仅是多了2次向量加载。

3.1.2 X86 架构上.NET 7.0程序的测试结果

X86架构上.NET 7.0程序的基准测试结果如下。

BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.3476)
AMD Ryzen 7 7840H w/ Radeon 780M Graphics, 1 CPU, 16 logical and 8 physical cores
.NET SDK 9.0.200[Host]     : .NET 7.0.20 (7.0.2024.26716), X64 RyuJIT AVX2DefaultJob : .NET 7.0.20 (7.0.2024.26716), X64 RyuJIT AVX2| Method            | Width | Mean        | Error     | StdDev    | Ratio | Code Size |
|------------------ |------ |------------:|----------:|----------:|------:|----------:|
| Scalar            | 1024  |  1,009.2 us |  10.62 us |   9.42 us |  1.00 |   1,673 B |
| UseVectors        | 1024  |    214.5 us |   4.05 us |   3.98 us |  0.21 |   3,724 B |
| UseVectorsArgs    | 1024  |    179.5 us |   3.47 us |   3.71 us |  0.18 |   4,031 B |
| UseVectorsX2AArgs | 1024  |    146.9 us |   2.89 us |   2.84 us |  0.15 |   3,912 B |
| UseVectorsX2BArgs | 1024  |    119.5 us |   2.39 us |   2.75 us |  0.12 |   3,673 B |
| ImageshopSse      | 1024  |    149.3 us |   2.92 us |   5.42 us |  0.15 |   1,350 B |
|                   |       |             |           |           |       |           |
| Scalar            | 2048  |  4,233.3 us |  48.45 us |  45.32 us |  1.00 |   1,673 B |
| UseVectors        | 2048  |  1,707.1 us |  21.99 us |  20.57 us |  0.40 |   3,724 B |
| UseVectorsArgs    | 2048  |  1,625.7 us |  14.62 us |  13.68 us |  0.38 |   4,031 B |
| UseVectorsX2AArgs | 2048  |  1,519.1 us |  19.57 us |  18.30 us |  0.36 |   3,912 B |
| UseVectorsX2BArgs | 2048  |  1,439.8 us |  16.77 us |  15.69 us |  0.34 |   3,673 B |
| ImageshopSse      | 2048  |  1,425.7 us |  18.37 us |  16.28 us |  0.34 |   1,350 B |
|                   |       |             |           |           |       |           |
| Scalar            | 4096  | 15,994.4 us | 134.29 us | 119.04 us |  1.00 |   1,673 B |
| UseVectors        | 4096  |  5,962.0 us |  76.95 us |  68.22 us |  0.37 |   3,709 B |
| UseVectorsArgs    | 4096  |  5,858.2 us |  74.10 us |  69.31 us |  0.37 |   4,016 B |
| UseVectorsX2AArgs | 4096  |  5,528.2 us |  34.26 us |  32.05 us |  0.35 |   3,897 B |
| UseVectorsX2BArgs | 4096  |  5,342.9 us |  51.69 us |  48.35 us |  0.33 |   3,658 B |
| ImageshopSse      | 4096  |  5,603.8 us |  38.53 us |  34.15 us |  0.35 |   1,350 B |

以1024时的测试结果为例,来观察向量化算法比起标量算法的性能提升。

  • UseVectors:1,009.2/214.5 ≈ 4.70。
  • UseVectorsArgs:1,009.2/179.5 ≈5.62。
  • UseVectorsX2AArgs:1,009.2/146.9 ≈6.87。
  • UseVectorsX2BArgs:1,009.2/119.5 ≈8.44。
  • ImageshopSse:1,009.2/149.3 ≈6.76。

与 .NET 6.0 时的测试结果差不多,UseVectorsX2BArgs 最快,UseVectorsX2AArgs、ImageshopSse 并列第2。

3.1.3 X86 架构上.NET 8.0程序的测试结果

X86架构上.NET 8.0程序的基准测试结果如下。

Vectors.Instance:       VectorTraits256Avx2     // Avx, Avx2, Sse, Sse2, Avx512VL
YShuffleX3Kernel_AcceleratedTypes:      SByte, Byte, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single, DoubleBenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.3476)
AMD Ryzen 7 7840H w/ Radeon 780M Graphics, 1 CPU, 16 logical and 8 physical cores
.NET SDK 9.0.200[Host]     : .NET 8.0.13 (8.0.1325.6609), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMIDefaultJob : .NET 8.0.13 (8.0.1325.6609), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI| Method            | Width | Mean         | Error     | StdDev    | Ratio | Code Size |
|------------------ |------ |-------------:|----------:|----------:|------:|----------:|
| Scalar            | 1024  |    565.61 us |  6.062 us |  5.671 us |  1.00 |        NA |
| UseVectors        | 1024  |     70.15 us |  0.946 us |  0.839 us |  0.12 |        NA |
| UseVectorsArgs    | 1024  |     71.35 us |  1.395 us |  2.368 us |  0.13 |        NA |
| UseVectorsX2AArgs | 1024  |     70.38 us |  1.389 us |  1.757 us |  0.12 |        NA |
| UseVectorsX2BArgs | 1024  |     71.11 us |  1.417 us |  1.325 us |  0.13 |        NA |
| ImageshopSse      | 1024  |    147.10 us |  3.065 us |  5.286 us |  0.28 |   1,304 B |
|                   |       |              |           |           |       |           |
| Scalar            | 2048  |  2,778.83 us | 31.741 us | 28.137 us |  1.00 |        NA |
| UseVectors        | 2048  |  1,021.40 us | 10.916 us | 10.211 us |  0.37 |        NA |
| UseVectorsArgs    | 2048  |  1,057.84 us | 20.079 us | 18.782 us |  0.38 |        NA |
| UseVectorsX2AArgs | 2048  |  1,057.32 us | 16.454 us | 15.391 us |  0.38 |        NA |
| UseVectorsX2BArgs | 2048  |  1,012.21 us | 13.793 us | 12.227 us |  0.36 |        NA |
| ImageshopSse      | 2048  |  1,742.22 us | 15.396 us | 14.401 us |  0.63 |   1,308 B |
|                   |       |              |           |           |       |           |
| Scalar            | 4096  | 11,051.36 us | 86.964 us | 77.092 us |  1.00 |        NA |
| UseVectors        | 4096  |  4,408.84 us | 48.341 us | 45.218 us |  0.40 |        NA |
| UseVectorsArgs    | 4096  |  4,330.39 us | 39.934 us | 35.401 us |  0.39 |        NA |
| UseVectorsX2AArgs | 4096  |  4,336.47 us | 48.908 us | 45.748 us |  0.39 |        NA |
| UseVectorsX2BArgs | 4096  |  4,083.04 us | 72.525 us | 67.840 us |  0.37 |        NA |
| ImageshopSse      | 4096  |  5,692.53 us | 53.488 us | 50.032 us |  0.52 |   1,311 B |

以1024时的测试结果为例,来观察向量化算法比起标量算法的性能提升。

  • UseVectors:565.61/70.15 ≈ 8.06。
  • UseVectorsArgs:565.61/71.35 ≈7.93。
  • UseVectorsX2AArgs:565.61/70.38 ≈8.04。
  • UseVectorsX2BArgs:565.61/71.11 ≈8.44。
  • ImageshopSse:565.61/147.10 ≈6.76。

其实,由于 .NET 8.0也优化了标量算法,这导致上面的的性能提升倍数看起来比较低。若拿 .NET 7.0的测试结果,与 .NET 8.0的UseVectors进行对比,就能看出差别了。

  • Scalar:1,120.3/70.15 ≈ 16.42。即 .NET 8.0向量算法的性能,是 .NET 7.0标量算法的 16.42 倍。
  • UseVectors:236.7/70.15 ≈ 3.47。即 .NET 8.0向量算法的性能,是 .NET 7.0向量算法的 3.47 倍。也可看做,Avx512的性能是Avx2的3.47倍。
  • UseVectorsX2AArgs:146.9/70.38 ≈2.09。
  • UseVectorsX2BArgs:119.5/71.11 ≈1.68。
  • ImageshopSse:149.3/147.10 ≈1.01。

此时 UseVectors、UseVectorsArgs、UseVectorsX2AArgs、UseVectorsX2BArgs 的成绩相差不大,考虑到测试误差,可以将它们并列第一。这是因为使用Avx512系列指令集之后,256位向量无论是“2向量换位”,还是“3向量换位”,都是仅需1条换位指令。

而 ImageshopSse 的性能保持不变,这是因为它固定使用了 Sse系列指令集。

3.2 Arm 架构

同样的源代码可以在 Arm 架构上运行。

3.2.1 Arm 架构上.NET 6.0程序的测试结果

Arm架构上.NET 6.0程序的基准测试结果如下。

Vectors.Instance:	VectorTraits128AdvSimdB64	// AdvSimd
YShuffleX3Kernel_AcceleratedTypes:	SByte, Byte, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single, DoubleBenchmarkDotNet v0.14.0, macOS Sequoia 15.1.1 (24B91) [Darwin 24.1.0]
Apple M2, 1 CPU, 8 logical and 8 physical cores
.NET SDK 9.0.102[Host]     : .NET 6.0.33 (6.0.3324.36610), Arm64 RyuJIT AdvSIMDDefaultJob : .NET 6.0.33 (6.0.3324.36610), Arm64 RyuJIT AdvSIMD| Method            | Width | Mean         | Error      | StdDev     | Ratio | RatioSD |
|------------------ |------ |-------------:|-----------:|-----------:|------:|--------:|
| Scalar            | 1024  |  1,504.09 us |   0.575 us |   0.480 us |  1.00 |    0.00 |
| UseVectors        | 1024  |    120.26 us |   1.569 us |   1.468 us |  0.08 |    0.00 |
| UseVectorsArgs    | 1024  |     83.77 us |   0.067 us |   0.056 us |  0.06 |    0.00 |
| UseVectorsX2AArgs | 1024  |     72.68 us |   0.034 us |   0.030 us |  0.05 |    0.00 |
| UseVectorsX2BArgs | 1024  |     82.61 us |   0.283 us |   0.265 us |  0.05 |    0.00 |
| ImageshopSse      | 1024  |           NA |         NA |         NA |     ? |       ? |
|                   |       |              |            |            |       |         |
| Scalar            | 2048  |  6,015.27 us |   5.786 us |   4.831 us |  1.00 |    0.00 |
| UseVectors        | 2048  |    479.44 us |   0.424 us |   0.397 us |  0.08 |    0.00 |
| UseVectorsArgs    | 2048  |    320.78 us |   0.212 us |   0.165 us |  0.05 |    0.00 |
| UseVectorsX2AArgs | 2048  |    332.22 us |   0.314 us |   0.263 us |  0.06 |    0.00 |
| UseVectorsX2BArgs | 2048  |    319.60 us |   1.490 us |   1.394 us |  0.05 |    0.00 |
| ImageshopSse      | 2048  |           NA |         NA |         NA |     ? |       ? |
|                   |       |              |            |            |       |         |
| Scalar            | 4096  | 24,709.98 us | 308.477 us | 288.549 us |  1.00 |    0.02 |
| UseVectors        | 4096  |  3,362.91 us |   1.807 us |   1.509 us |  0.14 |    0.00 |
| UseVectorsArgs    | 4096  |  2,840.79 us |  13.642 us |  12.760 us |  0.11 |    0.00 |
| UseVectorsX2AArgs | 4096  |  2,592.20 us |  25.326 us |  23.690 us |  0.10 |    0.00 |
| UseVectorsX2BArgs | 4096  |  2,843.72 us |  30.984 us |  28.982 us |  0.12 |    0.00 |
| ImageshopSse      | 4096  |           NA |         NA |         NA |     ? |       ? |

以1024时的测试结果为例,来观察向量化算法比起标量算法的性能提升。

  • UseVectors:1,504.09/120.26 ≈ 12.51。
  • UseVectorsArgs:1,504.09/ 83.77 ≈17.94。
  • UseVectorsX2AArgs:1,504.09/72.68 ≈20.69。
  • UseVectorsX2BArgs:1,504.09/82.61 ≈18.21。

注意一下 Vectors.Instance右侧的信息,会发现它使用了 AdvSimd 指令集。

因为现在是 Arm架构的处理器,没有Sse系列指令集,于是 ImageshopSse 没有测试结果。而 UseVectorsX2AArgs 等支持跨平台的测试函数,都有测试结果。

可以发现 UseVectorsX2AArgs 最快,而 UseVectorsX2BArgs 与 UseVectorsArgs 的性能差不多。看来在Arm平台上 UseVectorsX2BArgs 减少1次换位指令的优化,被它额外的2次向量加载给抵消了。

3.2.2 Arm 架构上.NET 7.0程序的测试结果

Arm架构上.NET 7.0程序的基准测试结果如下。

Vectors.Instance:	VectorTraits128AdvSimdB64	// AdvSimd
YShuffleX3Kernel_AcceleratedTypes:	SByte, Byte, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single, DoubleBenchmarkDotNet v0.14.0, macOS Sequoia 15.1.1 (24B91) [Darwin 24.1.0]
Apple M2, 1 CPU, 8 logical and 8 physical cores
.NET SDK 9.0.102[Host]     : .NET 7.0.20 (7.0.2024.26716), Arm64 RyuJIT AdvSIMDDefaultJob : .NET 7.0.20 (7.0.2024.26716), Arm64 RyuJIT AdvSIMD| Method            | Width | Mean         | Error     | StdDev    | Ratio | RatioSD |
|------------------ |------ |-------------:|----------:|----------:|------:|--------:|
| Scalar            | 1024  |  1,506.38 us |  2.527 us |  2.240 us |  1.00 |    0.00 |
| UseVectors        | 1024  |    108.38 us |  0.170 us |  0.159 us |  0.07 |    0.00 |
| UseVectorsArgs    | 1024  |     81.57 us |  0.070 us |  0.058 us |  0.05 |    0.00 |
| UseVectorsX2AArgs | 1024  |     69.35 us |  0.111 us |  0.098 us |  0.05 |    0.00 |
| UseVectorsX2BArgs | 1024  |     80.66 us |  0.104 us |  0.081 us |  0.05 |    0.00 |
| ImageshopSse      | 1024  |           NA |        NA |        NA |     ? |       ? |
|                   |       |              |           |           |       |         |
| Scalar            | 2048  |  6,014.79 us |  2.863 us |  2.235 us |  1.00 |    0.00 |
| UseVectors        | 2048  |    425.96 us |  0.234 us |  0.207 us |  0.07 |    0.00 |
| UseVectorsArgs    | 2048  |    317.95 us |  0.273 us |  0.228 us |  0.05 |    0.00 |
| UseVectorsX2AArgs | 2048  |    270.73 us |  0.238 us |  0.199 us |  0.05 |    0.00 |
| UseVectorsX2BArgs | 2048  |    308.50 us |  1.324 us |  1.239 us |  0.05 |    0.00 |
| ImageshopSse      | 2048  |           NA |        NA |        NA |     ? |       ? |
|                   |       |              |           |           |       |         |
| Scalar            | 4096  | 24,451.53 us | 31.420 us | 27.853 us |  1.00 |    0.00 |
| UseVectors        | 4096  |  3,263.99 us |  3.354 us |  2.801 us |  0.13 |    0.00 |
| UseVectorsArgs    | 4096  |  2,868.68 us |  7.482 us |  6.999 us |  0.12 |    0.00 |
| UseVectorsX2AArgs | 4096  |  2,512.38 us | 11.036 us |  9.783 us |  0.10 |    0.00 |
| UseVectorsX2BArgs | 4096  |  2,787.01 us |  4.692 us |  3.918 us |  0.11 |    0.00 |
| ImageshopSse      | 4096  |           NA |        NA |        NA |     ? |       ? |

以1024时的测试结果为例,来观察向量化算法比起标量算法的性能提升。

  • UseVectors:1,506.38/108.38 ≈ 13.90。
  • UseVectorsArgs:1,506.38/81.57 ≈18.47。
  • UseVectorsX2AArgs:1,506.38/69.35 ≈21.72。
  • UseVectorsX2BArgs:1,506.38/80.66 ≈18.68。

与 .NET 6.0 时的测试结果差不多,UseVectorsX2AArgs 最快,UseVectorsX2BArgs、UseVectorsArgs 并列第2。

3.2.3 Arm 架构上.NET 8.0程序的测试结果

Arm架构上.NET 8.0程序的基准测试结果如下。

BenchmarkDotNet v0.14.0, macOS Sequoia 15.1.1 (24B91) [Darwin 24.1.0]
Apple M2, 1 CPU, 8 logical and 8 physical cores
.NET SDK 9.0.102[Host]     : .NET 8.0.12 (8.0.1224.60305), Arm64 RyuJIT AdvSIMDDefaultJob : .NET 8.0.12 (8.0.1224.60305), Arm64 RyuJIT AdvSIMD| Method            | Width | Mean        | Error     | StdDev    | Ratio | RatioSD |
|------------------ |------ |------------:|----------:|----------:|------:|--------:|
| Scalar            | 1024  |   489.45 us |  9.667 us |  8.570 us |  1.00 |    0.02 |
| UseVectors        | 1024  |    60.78 us |  0.050 us |  0.045 us |  0.12 |    0.00 |
| UseVectorsArgs    | 1024  |    60.20 us |  0.621 us |  0.551 us |  0.12 |    0.00 |
| UseVectorsX2AArgs | 1024  |    61.02 us |  0.054 us |  0.045 us |  0.12 |    0.00 |
| UseVectorsX2BArgs | 1024  |    73.73 us |  0.159 us |  0.141 us |  0.15 |    0.00 |
| ImageshopSse      | 1024  |          NA |        NA |        NA |     ? |       ? |
|                   |       |             |           |           |       |         |
| Scalar            | 2048  | 1,904.18 us | 25.572 us | 23.920 us |  1.00 |    0.02 |
| UseVectors        | 2048  |   262.79 us |  0.482 us |  0.428 us |  0.14 |    0.00 |
| UseVectorsArgs    | 2048  |   266.08 us |  1.379 us |  1.290 us |  0.14 |    0.00 |
| UseVectorsX2AArgs | 2048  |   266.29 us |  0.949 us |  0.887 us |  0.14 |    0.00 |
| UseVectorsX2BArgs | 2048  |   297.26 us |  1.482 us |  1.237 us |  0.16 |    0.00 |
| ImageshopSse      | 2048  |          NA |        NA |        NA |     ? |       ? |
|                   |       |             |           |           |       |         |
| Scalar            | 4096  | 8,042.44 us | 17.405 us | 16.281 us |  1.00 |    0.00 |
| UseVectors        | 4096  | 2,307.59 us |  2.411 us |  1.882 us |  0.29 |    0.00 |
| UseVectorsArgs    | 4096  | 2,309.09 us |  4.411 us |  3.910 us |  0.29 |    0.00 |
| UseVectorsX2AArgs | 4096  | 2,193.09 us |  7.278 us |  6.078 us |  0.27 |    0.00 |
| UseVectorsX2BArgs | 4096  | 2,478.22 us |  3.373 us |  2.816 us |  0.31 |    0.00 |
| ImageshopSse      | 4096  |          NA |        NA |        NA |     ? |       ? |

以1024时的测试结果为例,来观察向量化算法比起标量算法的性能提升。

  • UseVectors:489.45/60.78 ≈ 8.05。
  • UseVectorsArgs:489.45/60.20 ≈8.13。
  • UseVectorsX2AArgs:489.45/61.02 ≈8.02。
  • UseVectorsX2BArgs:489.45/73.73 ≈ 6.64。

由于 .NET 8.0也优化了标量算法,这导致上面的的性能提升倍数看起来比较低。若拿 .NET 7.0的测试结果,与 .NET 8.0的UseVectors进行对比,就能看出差别了。

  • Scalar:1,504.47/60.78 ≈ 24.75。即 .NET 8.0向量算法的性能,是 .NET 7.0标量算法的 24.75 倍。
  • UseVectors:108.65/60.78 ≈1.79。
  • UseVectorsArgs:81.78/60.20 ≈ 1.36。即 .NET 8.0向量算法的性能,是 .NET 7.0向量算法的 1.36 倍。
  • UseVectorsX2AArgs:69.35/61.02 ≈1.17。
  • UseVectorsX2BArgs:80.66/73.73 ≈1.09。

此时 UseVectors、UseVectorsArgs、UseVectorsX2AArgs 的成绩相差不大,考虑到测试误差,可以将它们并列第一。这是因为使用AdvSimd的“多向量换位”指令之后,无论是“2向量换位”,还是“3向量换位”,都是仅需1条换位指令。

而 UseVectorsX2BArgs 的比 UseVectorsArgs 还慢了。看来对于 Arm架构,2次向量加载的开销也是颇大的。

3.3 .NET Framework

同样的源代码可以在 .NET Framework 上运行。基准测试结果如下。

Vectors.Instance:       VectorTraits256Base     //
YShuffleX3Kernel_AcceleratedTypes:      NoneBenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.3476)
AMD Ryzen 7 7840H w/ Radeon 780M Graphics, 1 CPU, 16 logical and 8 physical cores[Host]     : .NET Framework 4.8.1 (4.8.9290.0), X64 RyuJIT VectorSize=256DefaultJob : .NET Framework 4.8.1 (4.8.9290.0), X64 RyuJIT VectorSize=256| Method            | Width | Mean       | Error     | StdDev    | Ratio | RatioSD | Code Size |
|------------------ |------ |-----------:|----------:|----------:|------:|--------:|----------:|
| Scalar            | 1024  |   1.033 ms | 0.0177 ms | 0.0165 ms |  1.00 |    0.02 |   2,717 B |
| UseVectors        | 1024  |   6.161 ms | 0.0461 ms | 0.0409 ms |  5.96 |    0.10 |   4,883 B |
| UseVectorsArgs    | 1024  |   6.089 ms | 0.1066 ms | 0.0998 ms |  5.89 |    0.13 |   4,928 B |
| UseVectorsX2AArgs | 1024  |   6.349 ms | 0.0531 ms | 0.0497 ms |  6.15 |    0.11 |   5,288 B |
| UseVectorsX2BArgs | 1024  |   6.512 ms | 0.1288 ms | 0.1205 ms |  6.30 |    0.15 |   4,794 B |
|                   |       |            |           |           |       |         |           |
| Scalar            | 2048  |   4.284 ms | 0.0539 ms | 0.0504 ms |  1.00 |    0.02 |   2,717 B |
| UseVectors        | 2048  |  23.636 ms | 0.3372 ms | 0.3155 ms |  5.52 |    0.09 |   4,883 B |
| UseVectorsArgs    | 2048  |  23.650 ms | 0.2341 ms | 0.2190 ms |  5.52 |    0.08 |   4,928 B |
| UseVectorsX2AArgs | 2048  |  25.062 ms | 0.3512 ms | 0.3113 ms |  5.85 |    0.10 |   5,288 B |
| UseVectorsX2BArgs | 2048  |  25.362 ms | 0.3052 ms | 0.2706 ms |  5.92 |    0.09 |   4,794 B |
|                   |       |            |           |           |       |         |           |
| Scalar            | 4096  |  16.291 ms | 0.2417 ms | 0.2261 ms |  1.00 |    0.02 |   2,717 B |
| UseVectors        | 4096  |  94.486 ms | 1.5107 ms | 1.4131 ms |  5.80 |    0.11 |   4,883 B |
| UseVectorsArgs    | 4096  |  93.715 ms | 0.8965 ms | 0.7486 ms |  5.75 |    0.09 |   4,928 B |
| UseVectorsX2AArgs | 4096  |  99.979 ms | 1.9541 ms | 1.9192 ms |  6.14 |    0.14 |   5,288 B |
| UseVectorsX2BArgs | 4096  | 101.354 ms | 1.6959 ms | 1.5864 ms |  6.22 |    0.13 |   4,794 B |

UseVectors 等向量函数反而更慢了,这是因为 YShuffleX3Kernel 没有硬件加速。可以看到 “YShuffleX3Kernel_AcceleratedTypes”为“None”。

在实际使用时,应先检查YShuffleX3Kernel_AcceleratedTypes属性。当发现它没有硬件加速时,宜切换为标量算法。

四、结语

对于 Arm架构,很明显思路A(UseVectorsX2AArgs)的性能最好。

而对于 X86架构,情况有一些复杂——

  • 不支持Avx512时:思路B(UseVectorsX2BArgs)最快。思路A(UseVectorsX2AArgs)与ImageshopSse并列第2,都比 UseVectorsArgs 快。
  • 支持Avx512时:思路A(UseVectorsX2AArgs)、思路B(UseVectorsX2BArgs)与UseVectorsArgs等办法,均并列第1。

考虑到跨平台时代码的维护成本,选择维护唯一一套代码比较好。此时推荐 思路A(UseVectorsX2AArgs)。因为它在绝大多数时候(Arm、X86支持Avx512时)都是排第1,仅在不支持Avx512时才排到第2。而且它的代码相对更简单。

若追求极致性能,可考虑维护2套代码(思路A、思路B),随后根据是否支持Avx512来切换。

附录

  • 完整源代码: https://github.com/zyl910/VectorTraits.Sample.Benchmarks/blob/main/VectorTraits.Sample.Benchmarks.Inc/Image/ImageFlipXOn24bitBenchmark.cs
  • YShuffleX2Kernel 的文档: https://zyl910.github.io/VectorTraits_doc/api/Zyl.VectorTraits.Vectors.YShuffleX2Kernel.html
  • VectorTraits 的NuGet包: https://www.nuget.org/packages/VectorTraits
  • VectorTraits 的在线文档: https://zyl910.github.io/VectorTraits_doc/
  • VectorTraits 源代码: https://github.com/zyl910/VectorTraits
  • [C#] 对24位图像进行水平翻转(FlipX)的跨平台SIMD硬件加速向量算法(使用YShuffleX3Kernel)

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com