<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      zyl910

      優(yōu)化技巧、硬件體系、圖像處理、圖形學(xué)、游戲編程、國(guó)際化與文本信息處理。

        博客園 :: 首頁(yè) :: 博問 :: 閃存 :: 新隨筆 :: 聯(lián)系 :: 訂閱 訂閱 :: 管理 ::

      上一篇文章里,給大家講解了32位圖像水平翻轉(zhuǎn)(FlipX)算法,于是本文來探討更加復(fù)雜的24位圖像水平翻轉(zhuǎn)算法。
      本文除了會(huì)給出標(biāo)量算法外,還會(huì)給出向量算法。且這些算法是跨平臺(tái)的,同一份源代碼,能在 X86(Sse、Avx等指令集)及Arm(AdvSimd等指令集)等架構(gòu)上運(yùn)行,且均享有SIMD硬件加速。

      一、標(biāo)量算法

      1.1 算法實(shí)現(xiàn)

      標(biāo)量算法對(duì)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 基準(zhǔn)測(cè)試代碼

      使用 BenchmarkDotNet 進(jìn)行基準(zhǔn)測(cè)試。

      [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 難點(diǎn)說明

      24位像素的標(biāo)量算法改的很簡(jiǎn)單,但是24位像素的向量算法要復(fù)雜的多。

      這是因?yàn)橄蛄看笮∫话闶?16或32字節(jié)這樣的2的整數(shù)冪,而24位像素是3個(gè)字節(jié)一組,無法整除。這就給地址計(jì)算、數(shù)據(jù)處理等方面,帶來很大的難題。

      2.1.2 解決辦法:每次處理3個(gè)向量

      既然1個(gè)向量無法被3整除,那么我們干脆用3個(gè)向量。這樣肯定能被3整除。

      例如使用Sse指令集時(shí),向量大小為128位,即16個(gè)字節(jié)。3個(gè)向量,就是 48字節(jié),正好能放下16個(gè) 24位像素。

      隨后面臨一個(gè)難點(diǎn)——怎樣對(duì)3個(gè)向量?jī)?nèi)的24位像素進(jìn)行翻轉(zhuǎn)?

      根據(jù)前一篇文章的經(jīng)驗(yàn),處理1個(gè)向量?jī)?nèi)翻轉(zhuǎn)時(shí),可以使用Shuffle方法,只要構(gòu)造好索引就行。現(xiàn)在面對(duì)3個(gè)向量,若有適用于3個(gè)向量的換位方法就好了。

      為了解決這一難題,VectorTraits庫(kù)提供了YShuffleX3等方法。且由于能確保索引總是在有效范圍內(nèi),故還可以使用性能更好的 YShuffleX3Kernel 方法。

      在大多數(shù)時(shí)候,YShuffleX3Kernel 是利用單向量的shuffle指令組合而成。由于 .NET 8.0 增加了一批“多向量換位”的硬件指令,于是在以下平臺(tái),能獲得更好的硬件加速。

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

      詳見 [C#] .NET8增加了Arm架構(gòu)的多寄存器的查表函數(shù)(VectorTableLookup/VectorTableLookupExtension)

      YShuffleX3 在 .NET Framework 等平臺(tái)上運(yùn)行時(shí)是沒有硬件加速的,這是因?yàn)檫@些平臺(tái)不支持Sse等向量指令。可以通過 Vectors 的 YShuffleX3Kernel_AcceleratedTypes 屬性來得知哪些元素類型有硬件加速。當(dāng)發(fā)現(xiàn)不支持時(shí),宜切換為標(biāo)量算法。

      另外,還可以通過 Vectors.Instance.UsedInstructionSets 來查看該向量所使用的指令集。

      2.1.3 用YShuffleX3Kernel對(duì)3個(gè)向量?jī)?nèi)的24位像素進(jìn)行翻轉(zhuǎn)

      為了便于跨平臺(tái),這里使用了自動(dòng)大小向量Vector。且由于它的大小不固定,于是需要寫個(gè)循環(huán)來計(jì)算索引。根據(jù)上一篇文章的經(jīng)驗(yàn),我們可以在類的靜態(tài)構(gòu)造方法里做這個(gè)計(jì)算。

      private static readonly Vector<byte> _shuffleIndices0;
      private static readonly Vector<byte> _shuffleIndices1;
      private static readonly Vector<byte> _shuffleIndices2;
      
      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));
      }
      

      由于現(xiàn)在是需要對(duì)3個(gè)向量計(jì)算索引,故可以使用棧分配,創(chuàng)建一個(gè)3倍向量寬度的buf。計(jì)算好索引后,可以利用Span的Slice方法,分別加載這3個(gè)索引向量。

      索引計(jì)算好后,便可以用 YShuffleX3Kernel 來對(duì)3個(gè)向量做換位了。

      temp0 = Vectors.YShuffleX3Kernel(data0, data1, data2, _shuffleIndices0);
      temp1 = Vectors.YShuffleX3Kernel(data0, data1, data2, _shuffleIndices1);
      temp2 = Vectors.YShuffleX3Kernel(data0, data1, data2, _shuffleIndices2);
      

      隨后便可參考上一篇文章的思路,對(duì)整個(gè)圖像進(jìn)行水平翻轉(zhuǎn)。

      2.2 算法實(shí)現(xiàn)

      根據(jù)上面的思路,編寫代碼。源代碼如下。

      public static unsafe void UseVectorsDoBatch(byte* pSrc, int strideSrc, int width, int height, byte* pDst, int strideDst) {
          const int cbPixel = 3; // 24 bit: Bgr24, Rgb24.
          Vector<byte> indices0 = _shuffleIndices0;
          Vector<byte> indices1 = _shuffleIndices1;
          Vector<byte> indices2 = _shuffleIndices2;
          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.YShuffleX3Kernel(data0, data1, data2, indices0);
                  temp1 = Vectors.YShuffleX3Kernel(data0, data1, data2, indices1);
                  temp2 = Vectors.YShuffleX3Kernel(data0, data1, data2, indices2);
                  // 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;
          }
      }
      

      2.3 基準(zhǔn)測(cè)試代碼

      隨后為該算法編寫基準(zhǔn)測(cè)試代碼。

      [Benchmark]
      public void UseVectors() {
          UseVectorsDo(_sourceBitmapData, _destinationBitmapData, false);
      }
      
      //[Benchmark]
      public void UseVectorsParallel() {
          UseVectorsDo(_sourceBitmapData, _destinationBitmapData, true);
      }
      
      public static unsafe void UseVectorsDo(BitmapData src, BitmapData dst, bool useParallel = false) {
          int vectorWidth = Vector<byte>.Count;
          int width = src.Width;
          int height = src.Height;
          if (width <= vectorWidth) {
              ScalarDo(src, dst, useParallel);
              return;
          }
          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;
                  UseVectorsDoBatch(pSrc2, strideSrc, width, len, pDst2, strideDst);
              });
          } else {
              UseVectorsDoBatch(pSrc, strideSrc, width, height, pDst, strideDst);
          }
      }
      

      2.4 使用 YShuffleX3Kernel_Args 來做進(jìn)一步的優(yōu)化

      跟上篇文章所說的 YShuffleKernel 一樣,YShuffleX3Kernel 也提供了Args、Core后綴的方法。這用這些方法,可以將部分運(yùn)算從循環(huán)內(nèi),挪至循環(huán)前,從而提高了性能。

      源代碼如下。

      public static unsafe void UseVectorsArgsDoBatch(byte* pSrc, int strideSrc, int width, int height, byte* pDst, int strideDst) {
          const int cbPixel = 3; // 24 bit: Bgr24, Rgb24.
          Vectors.YShuffleX3Kernel_Args(_shuffleIndices0, out var indices0arg0, out var indices0arg1, out var indices0arg2, out var indices0arg3);
          Vectors.YShuffleX3Kernel_Args(_shuffleIndices1, out var indices1arg0, out var indices1arg1, out var indices1arg2, out var indices1arg3);
          Vectors.YShuffleX3Kernel_Args(_shuffleIndices2, 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.YShuffleX3Kernel(data0, data1, data2, _shuffleIndices0);
                  //temp1 = Vectors.YShuffleX3Kernel(data0, data1, data2, _shuffleIndices1);
                  //temp2 = Vectors.YShuffleX3Kernel(data0, data1, data2, _shuffleIndices2);
                  temp0 = Vectors.YShuffleX3Kernel_Core(data0, data1, data2, indices0arg0, indices0arg1, indices0arg2, indices0arg3);
                  temp1 = Vectors.YShuffleX3Kernel_Core(data0, data1, data2, indices1arg0, indices1arg1, indices1arg2, indices1arg3);
                  temp2 = Vectors.YShuffleX3Kernel_Core(data0, data1, data2, 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;
          }
      }
      

      三、基準(zhǔn)測(cè)試結(jié)果

      3.1 X86 架構(gòu)

      3.1.1 X86 架構(gòu)上.NET 6.0程序的測(cè)試結(jié)果。

      X86架構(gòu)上.NET 6.0程序的基準(zhǔn)測(cè)試結(jié)果如下。

      BenchmarkDotNet v0.14.0, Windows 11 (10.0.22631.4541/23H2/2023Update/SunValley3)
      AMD Ryzen 7 7840H w/ Radeon 780M Graphics, 1 CPU, 16 logical and 8 physical cores
      .NET SDK 8.0.403
        [Host]     : .NET 6.0.35 (6.0.3524.45918), X64 RyuJIT AVX2
        DefaultJob : .NET 6.0.35 (6.0.3524.45918), X64 RyuJIT AVX2
      
      
      | Method         | Width | Mean        | Error     | StdDev    | Ratio | RatioSD | Code Size |
      |--------------- |------ |------------:|----------:|----------:|------:|--------:|----------:|
      | Scalar         | 1024  |  1,110.8 us |  21.74 us |  22.33 us |  1.00 |    0.03 |   2,053 B |
      | UseVectors     | 1024  |    492.3 us |   9.74 us |  15.72 us |  0.44 |    0.02 |   4,505 B |
      | UseVectorsArgs | 1024  |    238.9 us |   3.14 us |   2.94 us |  0.22 |    0.00 |   4,234 B |
      |                |       |             |           |           |       |         |           |
      | Scalar         | 2048  |  4,430.0 us |  87.93 us |  94.08 us |  1.00 |    0.03 |   2,053 B |
      | UseVectors     | 2048  |  2,319.6 us |  18.62 us |  17.41 us |  0.52 |    0.01 |   4,505 B |
      | UseVectorsArgs | 2048  |  1,793.2 us |  34.57 us |  33.95 us |  0.40 |    0.01 |   4,234 B |
      |                |       |             |           |           |       |         |           |
      | Scalar         | 4096  | 16,536.4 us | 329.23 us | 618.37 us |  1.00 |    0.05 |   2,053 B |
      | UseVectors     | 4096  |  9,040.4 us | 104.73 us |  97.96 us |  0.55 |    0.02 |   4,490 B |
      | UseVectorsArgs | 4096  |  6,728.0 us | 120.28 us | 133.69 us |  0.41 |    0.02 |   4,219 B |
      
      • Scalar: 標(biāo)量算法。
      • UseVectors: 向量算法。
      • UseVectorsArgs: 使用Args將部分運(yùn)算挪至循環(huán)前的向量算法。

      以1024時(shí)的測(cè)試結(jié)果為例,來觀察向量化算法比起標(biāo)量算法的性能提升。

      • UseVectors:1,110.8/492.3 ≈ 2.26。即性能提升了 2.26 倍。
      • UseVectorsArgs:1,110.8/238.9 ≈4.65。即性能提升了 4.65 倍。

      將程序的輸出信息翻到最前面,注意看這2行信息。

      Vectors.Instance:       VectorTraits256Avx2     // Avx, Avx2, Sse, Sse2
      YShuffleX3Kernel_AcceleratedTypes:      SByte, Byte, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single, Double
      
      • Vectors.Instance: Vectors 用的是哪一套實(shí)現(xiàn)。“VectorTraits256Avx2”表示是256位Avx2指令集的實(shí)現(xiàn)。且它右側(cè)的“//”后面,給出了已使用指令集的名稱列表。例如現(xiàn)在是 Avx, Avx2, Sse, Sse2. (由于在組裝256位向量時(shí),有時(shí)需使用128位向量,故也使用了 Sse、Sse2 指令集)。
      • YShuffleX3Kernel_AcceleratedTypes: YShuffleX3Kernel的哪些元素類型有硬件加速。上面的代碼使用的是Byte類型,而該屬性含有Byte類型,故上面的代碼中的YShuffleX3Kernel是有硬件加速的。

      為了方便大家觀察所使用的指令集、是否有硬件極速,后面會(huì)將這2行信息放在基準(zhǔn)測(cè)試結(jié)果前,一起展示。

      3.1.2 X86 架構(gòu)上.NET 7.0程序的測(cè)試結(jié)果。

      X86架構(gòu)上.NET 7.0程序的基準(zhǔn)測(cè)試結(jié)果如下。

      Vectors.Instance:       VectorTraits256Avx2     // Avx, Avx2, Sse, Sse2
      YShuffleX3Kernel_AcceleratedTypes:      SByte, Byte, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single, Double
      
      BenchmarkDotNet v0.14.0, Windows 11 (10.0.22631.4541/23H2/2023Update/SunValley3)
      AMD Ryzen 7 7840H w/ Radeon 780M Graphics, 1 CPU, 16 logical and 8 physical cores
      .NET SDK 8.0.403
        [Host]     : .NET 7.0.20 (7.0.2024.26716), X64 RyuJIT AVX2
        DefaultJob : .NET 7.0.20 (7.0.2024.26716), X64 RyuJIT AVX2
      
      
      | Method         | Width | Mean        | Error     | StdDev    | Ratio | RatioSD | Code Size |
      |--------------- |------ |------------:|----------:|----------:|------:|--------:|----------:|
      | Scalar         | 1024  |  1,120.3 us |  22.39 us |  25.78 us |  1.00 |    0.03 |   1,673 B |
      | UseVectors     | 1024  |    236.7 us |   4.63 us |   5.69 us |  0.21 |    0.01 |   3,724 B |
      | UseVectorsArgs | 1024  |    209.5 us |   4.00 us |   4.45 us |  0.19 |    0.01 |   4,031 B |
      |                |       |             |           |           |       |         |           |
      | Scalar         | 2048  |  4,431.6 us |  65.38 us |  61.16 us |  1.00 |    0.02 |   1,673 B |
      | UseVectors     | 2048  |  1,866.8 us |  36.26 us |  48.41 us |  0.42 |    0.01 |   3,724 B |
      | UseVectorsArgs | 2048  |  1,889.9 us |  37.54 us |  74.97 us |  0.43 |    0.02 |   4,031 B |
      |                |       |             |           |           |       |         |           |
      | Scalar         | 4096  | 16,617.9 us | 329.75 us | 559.94 us |  1.00 |    0.05 |   1,673 B |
      | UseVectors     | 4096  |  6,337.2 us |  62.08 us |  55.03 us |  0.38 |    0.01 |   3,709 B |
      | UseVectorsArgs | 4096  |  6,408.1 us | 126.27 us | 118.11 us |  0.39 |    0.01 |   4,016 B |
      

      以1024時(shí)的測(cè)試結(jié)果為例,來觀察向量化算法比起標(biāo)量算法的性能提升。

      • UseVectors:1,120.3/236.7 ≈ 4.73。
      • UseVectorsArgs:1,120.3/209.5 ≈5.35。

      此時(shí)可以注意到,UseVectors與UseVectorsArgs的性能差距不大了。這是因?yàn)閺?.NET 7.0 開始,即時(shí)編譯器(JIT)會(huì)做優(yōu)化,自動(dòng)將循環(huán)內(nèi)的重復(fù)運(yùn)算挪至循環(huán)。故造成了差距不大的現(xiàn)象。

      3.1.3 X86 架構(gòu)上.NET 8.0程序的測(cè)試結(jié)果。

      X86架構(gòu)上.NET 8.0程序的基準(zhǔn)測(cè)試結(jié)果如下。

      Vectors.Instance:       VectorTraits256Avx2     // Avx, Avx2, Sse, Sse2, Avx512VL
      YShuffleX3Kernel_AcceleratedTypes:      SByte, Byte, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single, Double
      
      BenchmarkDotNet v0.14.0, Windows 11 (10.0.22631.4541/23H2/2023Update/SunValley3)
      AMD Ryzen 7 7840H w/ Radeon 780M Graphics, 1 CPU, 16 logical and 8 physical cores
      .NET SDK 8.0.403
        [Host]     : .NET 8.0.10 (8.0.1024.46610), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
        DefaultJob : .NET 8.0.10 (8.0.1024.46610), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
      
      
      | Method         | Width | Mean        | Error      | StdDev     | Ratio | RatioSD |
      |--------------- |------ |------------:|-----------:|-----------:|------:|--------:|
      | Scalar         | 1024  |   549.22 us |  10.876 us |  11.637 us |  1.00 |    0.03 |
      | UseVectors     | 1024  |    68.21 us |   1.326 us |   2.142 us |  0.12 |    0.00 |
      | UseVectorsArgs | 1024  |    68.71 us |   1.360 us |   2.453 us |  0.13 |    0.01 |
      |                |       |             |            |            |       |         |
      | Scalar         | 2048  | 2,704.83 us |  53.643 us |  92.531 us |  1.00 |    0.05 |
      | UseVectors     | 2048  | 1,014.52 us |   8.824 us |   7.822 us |  0.38 |    0.01 |
      | UseVectorsArgs | 2048  | 1,020.66 us |  15.739 us |  14.723 us |  0.38 |    0.01 |
      |                |       |             |            |            |       |         |
      | Scalar         | 4096  | 9,778.60 us | 114.022 us | 106.656 us |  1.00 |    0.01 |
      | UseVectors     | 4096  | 4,360.43 us |  60.832 us |  56.903 us |  0.45 |    0.01 |
      | UseVectorsArgs | 4096  | 4,341.89 us |  82.877 us | 101.780 us |  0.44 |    0.01 |
      

      以1024時(shí)的測(cè)試結(jié)果為例,來觀察向量化算法比起標(biāo)量算法的性能提升。

      • UseVectors:549.22/68.21 ≈ 8.05。
      • UseVectorsArgs:549.22/68.71 ≈7.99。

      性能大幅度提升!這是因?yàn)?.NET 8.0 支持了Avx512系列指令集,且這個(gè)CPU支持。對(duì)比一下 Vectors.Instance右側(cè)的信息,會(huì)發(fā)現(xiàn)現(xiàn)在多了 Avx512VL 指令集。在Avx512系列指令集中,Avx512VL就是負(fù)責(zé)處理128~256位數(shù)據(jù)的指令集。

      其實(shí),由于 .NET 8.0也優(yōu)化了標(biāo)量算法,這導(dǎo)致上面的的性能提升倍數(shù)看起來比較低。若拿 .NET 7.0的測(cè)試結(jié)果,與 .NET 8.0的UseVectors進(jìn)行對(duì)比,就能看出差別了。

      • Scalar:1,120.3/68.21 ≈ 16.42。即 .NET 8.0向量算法的性能,是 .NET 7.0標(biāo)量算法的 16.42 倍。
      • UseVectors:236.7/68.21 ≈ 3.47。即 .NET 8.0向量算法的性能,是 .NET 7.0向量算法的 3.47 倍。也可看做,Avx512的性能是Avx2的3.47倍。

      同樣是256位向量寬度,Avx512為什么能快這么多?這是因?yàn)锳vx2沒有提供“跨小道(lane)重排指令”,導(dǎo)致需要使用2條shuffle指令才能實(shí)現(xiàn)全256位的換位。而Avx512不僅提供了“跨小道重排指令”(_mm_permutexvar_epi8),且提供了“2向量的跨小道重排指令”(_mm_permutex2var_epi8)。再加上內(nèi)部還可以利用512位寄存器進(jìn)行進(jìn)一步優(yōu)化,于是性能提升了很多。(下一篇文章會(huì)詳細(xì)講解)

      3.2 Arm 架構(gòu)

      同樣的源代碼可以在 Arm 架構(gòu)上運(yùn)行。

      3.2.1 Arm 架構(gòu)上.NET 6.0程序的測(cè)試結(jié)果。

      Arm架構(gòu)上.NET 6.0程序的基準(zhǔn)測(cè)試結(jié)果如下。

      Vectors.Instance:	VectorTraits128AdvSimdB64	// AdvSimd
      YShuffleX3Kernel_AcceleratedTypes:	SByte, Byte, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single, Double
      
      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 8.0.204
        [Host]     : .NET 6.0.33 (6.0.3324.36610), Arm64 RyuJIT AdvSIMD
        DefaultJob : .NET 6.0.33 (6.0.3324.36610), Arm64 RyuJIT AdvSIMD
      
      
      | Method         | Width | Mean         | Error     | StdDev    | Ratio |
      |--------------- |------ |-------------:|----------:|----------:|------:|
      | Scalar         | 1024  |  1,504.84 us |  0.449 us |  0.375 us |  1.00 |
      | UseVectors     | 1024  |    119.36 us |  0.042 us |  0.040 us |  0.08 |
      | UseVectorsArgs | 1024  |     83.89 us |  0.160 us |  0.149 us |  0.06 |
      |                |       |              |           |           |       |
      | Scalar         | 2048  |  6,011.17 us |  1.346 us |  1.193 us |  1.00 |
      | UseVectors     | 2048  |    476.02 us |  6.485 us |  6.066 us |  0.08 |
      | UseVectorsArgs | 2048  |    328.52 us |  0.298 us |  0.264 us |  0.05 |
      |                |       |              |           |           |       |
      | Scalar         | 4096  | 24,403.68 us |  6.763 us |  6.326 us |  1.00 |
      | UseVectors     | 4096  |  3,378.05 us |  1.674 us |  1.566 us |  0.14 |
      | UseVectorsArgs | 4096  |  2,852.52 us | 22.086 us | 20.660 us |  0.12 |
      

      以1024時(shí)的測(cè)試結(jié)果為例,來觀察向量化算法比起標(biāo)量算法的性能提升。

      • UseVectors:1,504.84/119.36 ≈ 12.61。
      • UseVectorsArgs:1,504.84/83.89 ≈17.94。

      注意一下 Vectors.Instance右側(cè)的信息,會(huì)發(fā)現(xiàn)它使用了 AdvSimd 指令集。

      3.2.2 Arm 架構(gòu)上.NET 7.0程序的測(cè)試結(jié)果。

      Arm架構(gòu)上.NET 7.0程序的基準(zhǔn)測(cè)試結(jié)果如下。

      Vectors.Instance:	VectorTraits128AdvSimdB64	// AdvSimd
      YShuffleX3Kernel_AcceleratedTypes:	SByte, Byte, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single, Double
      
      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 8.0.204
        [Host]     : .NET 7.0.20 (7.0.2024.26716), Arm64 RyuJIT AdvSIMD
        DefaultJob : .NET 7.0.20 (7.0.2024.26716), Arm64 RyuJIT AdvSIMD
      
      
      | Method         | Width | Mean         | Error    | StdDev   | Ratio |
      |--------------- |------ |-------------:|---------:|---------:|------:|
      | Scalar         | 1024  |  1,504.47 us | 0.639 us | 0.566 us |  1.00 |
      | UseVectors     | 1024  |    108.65 us | 0.139 us | 0.123 us |  0.07 |
      | UseVectorsArgs | 1024  |     81.78 us | 0.142 us | 0.133 us |  0.05 |
      |                |       |              |          |          |       |
      | Scalar         | 2048  |  6,014.20 us | 2.201 us | 1.718 us |  1.00 |
      | UseVectors     | 2048  |    427.18 us | 0.286 us | 0.267 us |  0.07 |
      | UseVectorsArgs | 2048  |    318.35 us | 0.373 us | 0.330 us |  0.05 |
      |                |       |              |          |          |       |
      | Scalar         | 4096  | 24,403.88 us | 6.181 us | 5.480 us |  1.00 |
      | UseVectors     | 4096  |  3,280.84 us | 4.771 us | 4.463 us |  0.13 |
      | UseVectorsArgs | 4096  |  2,873.47 us | 4.675 us | 4.373 us |  0.12 |
      

      以1024時(shí)的測(cè)試結(jié)果為例,來觀察向量化算法比起標(biāo)量算法的性能提升。

      • UseVectors:1,504.47/108.65 ≈ 13.85。
      • UseVectorsArgs:1,504.47/81.78 ≈18.40。

      性能稍有提升。

      3.2.3 Arm 架構(gòu)上.NET 8.0程序的測(cè)試結(jié)果。

      Arm架構(gòu)上.NET 8.0程序的基準(zhǔn)測(cè)試結(jié)果如下。

      Vectors.Instance:	VectorTraits128AdvSimdB64	// AdvSimd
      YShuffleX3Kernel_AcceleratedTypes:	SByte, Byte, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single, Double
      
      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 8.0.204
        [Host]     : .NET 8.0.4 (8.0.424.16909), Arm64 RyuJIT AdvSIMD
        DefaultJob : .NET 8.0.4 (8.0.424.16909), Arm64 RyuJIT AdvSIMD
      
      
      | Method         | Width | Mean        | Error     | StdDev    | Ratio |
      |--------------- |------ |------------:|----------:|----------:|------:|
      | Scalar         | 1024  |   478.43 us |  2.053 us |  1.921 us |  1.00 |
      | UseVectors     | 1024  |    61.18 us |  0.677 us |  0.633 us |  0.13 |
      | UseVectorsArgs | 1024  |    61.93 us |  0.225 us |  0.199 us |  0.13 |
      |                |       |             |           |           |       |
      | Scalar         | 2048  | 1,891.65 us |  5.621 us |  4.693 us |  1.00 |
      | UseVectors     | 2048  |   260.20 us |  0.201 us |  0.179 us |  0.14 |
      | UseVectorsArgs | 2048  |   263.75 us |  0.851 us |  0.796 us |  0.14 |
      |                |       |             |           |           |       |
      | Scalar         | 4096  | 7,900.34 us | 91.227 us | 85.333 us |  1.00 |
      | UseVectors     | 4096  | 2,310.99 us | 17.264 us | 14.416 us |  0.29 |
      | UseVectorsArgs | 4096  | 2,310.74 us |  1.605 us |  1.423 us |  0.29 |
      

      以1024時(shí)的測(cè)試結(jié)果為例,來觀察向量化算法比起標(biāo)量算法的性能提升。

      • UseVectors:478.43/61.18 ≈ 7.82。
      • UseVectorsArgs:478.43/61.93 ≈7.73。

      由于 .NET 8.0也優(yōu)化了標(biāo)量算法,這導(dǎo)致上面的的性能提升倍數(shù)看起來比較低。若拿 .NET 7.0的測(cè)試結(jié)果,與 .NET 8.0的UseVectors進(jìn)行對(duì)比,就能看出差別了。

      • Scalar:1,504.47/61.18 ≈ 24.59。即 .NET 8.0向量算法的性能,是 .NET 7.0標(biāo)量算法的 24.59 倍。
      • UseVectors:108.65/61.18 ≈ 1.78。
      • UseVectorsArgs:81.78/61.93 ≈ 1.32。即 .NET 8.0向量算法的性能,是 .NET 7.0向量算法的 1.32 倍。

      可看出,性能有較大提升。

      同樣是128位向量寬度, .NET 8.0為什么能快這么多?這是因?yàn)?.NET 8.0 新增了對(duì) AdvSimd指令集里的“2-4向量查表”指令的支持。其實(shí)Arm很早就有了這些指令,只是 .NET直到.NET 8.0 時(shí)才將這些指令給集成進(jìn)來。

      使用VectorTraits庫(kù),您只需升級(jí)到 .NET 8.0,同樣的源代碼在編譯時(shí)會(huì)自動(dòng)切換為最佳的硬件指令。

      3.3 .NET Framework

      同樣的源代碼可以在 .NET Framework 上運(yùn)行。基準(zhǔn)測(cè)試結(jié)果如下。

      Vectors.Instance:       VectorTraits256Base     //
      YShuffleX3Kernel_AcceleratedTypes:      None
      
      BenchmarkDotNet v0.14.0, Windows 11 (10.0.22631.4541/23H2/2023Update/SunValley3)
      AMD Ryzen 7 7840H w/ Radeon 780M Graphics, 1 CPU, 16 logical and 8 physical cores
        [Host]     : .NET Framework 4.8.1 (4.8.9282.0), X64 RyuJIT VectorSize=256
        DefaultJob : .NET Framework 4.8.1 (4.8.9282.0), X64 RyuJIT VectorSize=256
      
      
      | Method         | Width | Mean        | Error       | StdDev      | Ratio | RatioSD | Code Size |
      |--------------- |------ |------------:|------------:|------------:|------:|--------:|----------:|
      | Scalar         | 1024  |    999.7 us |    14.16 us |    11.82 us |  1.00 |    0.02 |   2,717 B |
      | UseVectors     | 1024  |  6,040.0 us |    57.76 us |    54.03 us |  6.04 |    0.09 |        NA |
      | UseVectorsArgs | 1024  |  5,896.4 us |   105.77 us |    98.94 us |  5.90 |    0.12 |        NA |
      |                |       |             |             |             |       |         |           |
      | Scalar         | 2048  |  4,267.0 us |    74.72 us |    69.90 us |  1.00 |    0.02 |   2,717 B |
      | UseVectors     | 2048  | 23,070.7 us |   250.11 us |   221.72 us |  5.41 |    0.10 |        NA |
      | UseVectorsArgs | 2048  | 23,106.7 us |   241.23 us |   201.44 us |  5.42 |    0.10 |        NA |
      |                |       |             |             |             |       |         |           |
      | Scalar         | 4096  | 15,977.6 us |   308.91 us |   489.96 us |  1.00 |    0.04 |   2,717 B |
      | UseVectors     | 4096  | 91,944.4 us | 1,152.83 us | 1,078.36 us |  5.76 |    0.19 |        NA |
      | UseVectorsArgs | 4096  | 92,677.3 us | 1,555.69 us | 1,527.90 us |  5.81 |    0.20 |        NA |
      

      UseVectors 反而更慢了,這是因?yàn)?YShuffleX3Kernel 沒有硬件加速。可以看到 “YShuffleX3Kernel_AcceleratedTypes”為“None”。

      在實(shí)際使用時(shí),應(yīng)先檢查YShuffleX3Kernel_AcceleratedTypes屬性。當(dāng)發(fā)現(xiàn)它沒有硬件加速時(shí),宜切換為標(biāo)量算法。

      四、結(jié)語(yǔ)

      VectorTraits庫(kù)提供了完善的多向量換位的功能,能對(duì) 2~4個(gè)向量進(jìn)行換位。它們的名稱如下。

      • 2個(gè)向量: YShuffleX2, YShuffleX2Insert, YShuffleX2Kernel。
      • 3個(gè)向量: YShuffleX3, YShuffleX3Insert, YShuffleX3Kernel。
      • 4個(gè)向量: YShuffleX4, YShuffleX4Insert, YShuffleX4Kernel。

      使用這些方法,能幫您解決很多算法的向量化改造難題。

      附錄

      posted on 2024-12-04 21:44  zyl910  閱讀(124)  評(píng)論(2)    收藏  舉報(bào)
      主站蜘蛛池模板: 精品综合一区二区三区四区| 国产成人精品无码播放| 国产黄色一区二区三区四区| 久青草国产在视频在线观看| 久久天堂无码av网站| 真实单亲乱l仑对白视频| 日本黄色三级一区二区三区 | 精品少妇后入一区二区三区| 国产成人免费永久在线平台| 婷婷综合缴情亚洲| 人妻熟妇乱又伦精品无码专区| 老妇xxxxx性开放| 国产又色又爽又刺激在线观看 | 2021国产精品视频网站| 日韩精品在线观看一二区| 亚洲中文字幕无码av永久| 日本一区二区精品色超碰| 中文字幕网红自拍偷拍视频| 国产在线98福利播放视频| 欧美成人www免费全部网站| 日韩精品国产二区三区| 国产无套内射普通话对白| 久久精品夜色国产亚洲av| 欧美交a欧美精品喷水| 精品人妻伦九区久久aaa片69| 亚洲国产精品成人综合色在| 狠狠做五月深爱婷婷伊人| 亚洲精品国模一区二区| 柠檬福利第一导航在线| 在线A毛片免费视频观看| 东方四虎在线观看av| 国内熟妇人妻色在线三级| 91精品国产免费人成网站| 国产成人理论在线视频观看| 国产av一区二区不卡| 性中国videossexo另类| 久久精品夜夜夜夜夜久久| 亚洲精品777| 久章草在线毛片视频播放| 久久国产热这里只有精品| 国产精品亚洲а∨天堂2021|