<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é)、游戲編程、國際化與文本信息處理。

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

      在本主題的 上一篇文章里,給大家講解了24位圖像水平翻轉(zhuǎn)(FlipX)算法。但該文章主要是為了介紹 YShuffleX3Kernel 的使用,該算法性能并不是最優(yōu)的。于是本文將介紹如何使用 YShuffleX2Kernel 來優(yōu)化程序。而且Imageshop在留言區(qū)給了一份C語言的、基于Sse系列指令集實(shí)現(xiàn)的代碼,正好一起對比一下。

      一、算法思路

      1.1 瓶頸分析

      當(dāng)硬件沒有多向量換位(shuffle)的指令時(shí),一般來說需要3條換位指令才能實(shí)現(xiàn) YShuffleX3Kernel 方法。

      對于24位圖像水平翻轉(zhuǎn)來說,每次內(nèi)循環(huán)是處理3個(gè)向量長度的數(shù)據(jù),需調(diào)用3次 YShuffleX3Kernel 方法,故共調(diào)用了 3*3=9 條換位指令。

      且在使用X86平臺的Avx2指令集時(shí),因它沒有提供“跨小道(lane)換位”指令,導(dǎo)致需要用2條換位指令來實(shí)現(xiàn)“向量內(nèi)全范圍的換位”。于是對于24位圖像水平翻轉(zhuǎn)來說,故共調(diào)用了 3*3*2=18 條換位指令。這個(gè)數(shù)量很大了。

      于是,若能降低換位指令的數(shù)量,將能提升程序的性能。

      1.2 優(yōu)化思路

      X86架構(gòu)的Sse指令集,與Arm架構(gòu)的AdvSimd指令集里均使用128位的向量寄存器,即16個(gè)字節(jié)。

      24位像素是3個(gè)字節(jié)一組。對于16個(gè)字節(jié)向量寄存器來說,僅能完整存放 5個(gè)像素((int)(16 / 3) = 5),占據(jù)了 3*5=15 個(gè)字節(jié)。剩余的1個(gè)字節(jié)超出向量大小的邊界了,位于另一個(gè)向量中。

      換個(gè)角度來看,使用1條單向量的換位指令,能將16個(gè)字節(jié)中的15個(gè)字節(jié)做好水平翻轉(zhuǎn)。僅剩1個(gè)字節(jié)的數(shù)據(jù)不正確,此時(shí)可以另想辦法來修正。

      一種思路是用標(biāo)量的內(nèi)存讀寫語句來修正這些不正確的字節(jié)。這就是 Imageshop的Sse版算法的思路,對于每次處理48個(gè)字節(jié)的內(nèi)循環(huán),使用標(biāo)量的內(nèi)存讀寫語句修正了6個(gè)字節(jié)的數(shù)據(jù)。這種辦法能將換位指令數(shù)量降到了最低,但代價(jià)是增加了標(biāo)量的內(nèi)存讀寫工作。需要進(jìn)行基準(zhǔn)測試,實(shí)際對比性能。

      另一種思路是再用1條換位指令來計(jì)算剩余字節(jié)的數(shù)據(jù),然后使用條件掩碼將這2種結(jié)果(15個(gè)字節(jié)+剩余1字節(jié))合并。而且大多數(shù)架構(gòu)(X86、Arm)的換位指令帶有清零能力,小心的調(diào)整掩碼,能使無效字節(jié)為0,這樣僅需 or 指令就能將2種結(jié)果合并了。

      上面這種計(jì)算,正好是“2向量的換位”操作。X86架構(gòu)的Avx512系列指令集,以及Arm架構(gòu)的AdvSimd指令集,均提供了這樣的指令。而且 .NET 8.0 支持了這些指令的內(nèi)在函數(shù)(Intrinsic Functions)。

      手動(dòng)調(diào)用內(nèi)在函數(shù)是很繁瑣的,且難以跨平臺。于是 VectorTraits 提供了 YShuffleX2Kernel 方法。它會檢查硬件是否支持“2向量的換位”指令,如支持便會使用各平臺的這些指令;若不支持,便會自動(dòng)計(jì)算好掩碼,調(diào)用2條換位指令并組合。

      也就是說,當(dāng)硬件沒有多向量換位的指令時(shí),YShuffleX2Kernel 比起 YShuffleX3Kernel,能少使用1個(gè)換位指令。從而降低了開銷,優(yōu)化了性能。

      1.3 計(jì)算索引

      對于24位圖像水平翻轉(zhuǎn)來說,每次內(nèi)循環(huán)是處理3個(gè)向量長度的數(shù)據(jù),此時(shí)需要3個(gè)索引向量。其中 索引0、索引2,僅需訪問2個(gè)數(shù)據(jù)向量,正好能使用 YShuffleX2Kernel。而中間的索引1,默認(rèn)情況會訪問3個(gè)數(shù)據(jù)向量。下面的表格詳細(xì)說明了這些情況。

      Name offset 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
      Indice 0 45 46 47 42 43 44 39 40 41 36 37 38 33 34 35 30 31 32 27 28 29 24 25 26 21 22 23 18 19 20 15 16 17 12 13 14 9 10 11 6 7 8 3 4 5 0 1 2
      Indice0 16 29 30 31 26 27 28 23 24 25 20 21 22 17 18 19 14
      Indice1E 16 15 16 11 12 13 8 9 10 5 6 7 2 3 4 -1 0
      Indice1A 0 31 32 27 28 29 24 25 26 21 22 23 18 19 20 15 16
      Indice1B 15 16 17 12 13 14 9 10 11 6 7 8 3 4 5 0 1
      Indice2 0 17 12 13 14 9 10 11 6 7 8 3 4 5 0 1 2

      變量說明:

      • Indice: YShuffleX3Kernel 的索引。3個(gè)索引均寫在同一行。
      • Indice0: YShuffleX2Kernel 的索引0。
      • Indice1E: 演示了錯(cuò)誤方案下的索引1。
      • Indice1A: 索引1使用A方案。
      • Indice1B: 索引1使用B方案。
      • Indice2: YShuffleX2Kernel 的索引2。

      索引2(Indice2)最簡單,直接使用 YShuffleX3Kernel的索引2就行。因它僅需訪問第0個(gè)與第1個(gè)輸入向量。

      索引0(Indice2)稍微復(fù)雜一點(diǎn)。它僅需訪問第1個(gè)與第2個(gè)輸入向量,于是從數(shù)據(jù)地址的偏移量(offset)來說,偏移量為16。即向量寄存器的字節(jié)數(shù)(Vector<byte>.Count)。

      索引1最麻煩,因?yàn)樗枰L問3個(gè)向量。于是上表給出了 Indice1E 這一行,將 offset 設(shè)為16,于是可以觀察到它不僅有小于下界的值(-1),還有超過上界的值(16)。有2種思路來解決這一難題:

      • A. 干脆用 YShuffleX3Kernel 來計(jì)算它。于是可以直接使用 YShuffleX3Kernel 時(shí)的索引1,即 Indice1A。也就說,內(nèi)循環(huán)會調(diào)用2次 YShuffleX2Kernel,和1次 YShuffleX3Kernel,比起調(diào)用3次YShuffleX3Kernel的開銷低。
      • B. 干脆為它提供的輸入向量。此時(shí)用“偏移量15”來加載2筆連續(xù)的向量數(shù)據(jù),便能用 YShuffleX2Kernel 來計(jì)算了。由于現(xiàn)在數(shù)據(jù)加載地址很近(偏移15與偏移16很近),且現(xiàn)在處理器的高速緩存(Cache)技術(shù)很成熟,使得這種加載的開銷很低。

      隨后可以通過基準(zhǔn)測試,來看哪種思路的性能更好。

      二、算法實(shí)現(xiàn)

      2.1 程序里計(jì)算索引

      首先需要計(jì)算索引。可以在先前YShuffleX3Kernel的索引計(jì)算代碼上修改,關(guān)鍵是處理好 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
      }
      

      可見,代碼改動(dòng)不多。僅需使用 Vector.Subtract 做向量減法,減去偏移量。

      2.2 思路A的實(shí)現(xiàn)

      根據(jù)上面的思路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后綴的方法可以將部分運(yùn)算從循環(huán)內(nèi),挪至循環(huán)前,從而提高了性能。于是本函數(shù)便使用了這個(gè)辦法。

      2.3 思路B的實(shí)現(xiàn)

      根據(jù)上面的思路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的內(nèi)循環(huán),全部使用 YShuffleX2Kernel_Core 來計(jì)算。只是需要為索引1(_shuffleX2Indices1B),加載其特有偏移量的2個(gè)向量(dataB0、dataB1)。由于偏移量是基于字節(jié)的,于是在在計(jì)算地址時(shí),需將指針p轉(zhuǎn)為了 byte*類型后再加偏移量,最后再轉(zhuǎn)回 Vector<byte>* 類型來做向量加載。

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

      3.1 X86 架構(gòu)

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

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

      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 AVX2
        DefaultJob : .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: 標(biāo)量算法。
      • UseVectors: 向量算法。
      • UseVectorsArgs: 使用Args將部分運(yùn)算挪至循環(huán)前的向量算法。
      • UseVectorsX2AArgs: 思路A的算法(2次YShuffleX2Kernel + 1次YShuffleX3Kernel)。
      • UseVectorsX2BArgs: 思路B的算法(3次YShuffleX2Kernel,并處理特殊偏移 )。
      • ImageshopSse: Imageshop的Sse版算法,翻譯為 C# 語言。

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

      • 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 倍。

      可以發(fā)現(xiàn),減少換位指令數(shù)量的辦法確實(shí)有效,UseVectorsX2AArgs 等函數(shù)的速度,均比 UseVectorsArgs 要快。

      還可發(fā)現(xiàn),內(nèi)循環(huán)僅使用3次換位的ImageshopSse ,性能與 UseVectorsX2AArgs 差不多。由于AVX2沒有“跨小道(lane)換位”指令,導(dǎo)致需要用2條換位指令來實(shí)現(xiàn)“向量內(nèi)全范圍的換位”,于是UseVectorsX2AArgs的內(nèi)循環(huán)里有 (2+3+2) * 2 = 14 條換位指令。雖然換位指令的數(shù)量相差這么多,但性能差不多,說明標(biāo)量內(nèi)存讀寫的開銷頗大。

      UseVectorsX2BArgs 的速度最快。這是因?yàn)?UseVectorsX2BArgs的內(nèi)循環(huán) 比 UseVectorsX2AArgs 少一次換位操作,代價(jià)僅是多了2次向量加載。

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

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

      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 AVX2
        DefaultJob : .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時(shí)的測試結(jié)果為例,來觀察向量化算法比起標(biāo)量算法的性能提升。

      • 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 時(shí)的測試結(jié)果差不多,UseVectorsX2BArgs 最快,UseVectorsX2AArgs、ImageshopSse 并列第2。

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

      X86架構(gòu)上.NET 8.0程序的基準(zhǔn)測試結(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.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+VBMI
        DefaultJob : .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時(shí)的測試結(jié)果為例,來觀察向量化算法比起標(biāo)量算法的性能提升。

      • 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。

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

      • Scalar:1,120.3/70.15 ≈ 16.42。即 .NET 8.0向量算法的性能,是 .NET 7.0標(biāo)量算法的 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。

      此時(shí) UseVectors、UseVectorsArgs、UseVectorsX2AArgs、UseVectorsX2BArgs 的成績相差不大,考慮到測試誤差,可以將它們并列第一。這是因?yàn)槭褂肁vx512系列指令集之后,256位向量無論是“2向量換位”,還是“3向量換位”,都是僅需1條換位指令。

      而 ImageshopSse 的性能保持不變,這是因?yàn)樗潭ㄊ褂昧?Sse系列指令集。

      3.2 Arm 架構(gòu)

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

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

      Arm架構(gòu)上.NET 6.0程序的基準(zhǔn)測試結(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 9.0.102
        [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 | 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時(shí)的測試結(jié)果為例,來觀察向量化算法比起標(biāo)量算法的性能提升。

      • 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右側(cè)的信息,會發(fā)現(xiàn)它使用了 AdvSimd 指令集。

      因?yàn)楝F(xiàn)在是 Arm架構(gòu)的處理器,沒有Sse系列指令集,于是 ImageshopSse 沒有測試結(jié)果。而 UseVectorsX2AArgs 等支持跨平臺的測試函數(shù),都有測試結(jié)果。

      可以發(fā)現(xiàn) UseVectorsX2AArgs 最快,而 UseVectorsX2BArgs 與 UseVectorsArgs 的性能差不多。看來在Arm平臺上 UseVectorsX2BArgs 減少1次換位指令的優(yōu)化,被它額外的2次向量加載給抵消了。

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

      Arm架構(gòu)上.NET 7.0程序的基準(zhǔn)測試結(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 9.0.102
        [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 | 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時(shí)的測試結(jié)果為例,來觀察向量化算法比起標(biāo)量算法的性能提升。

      • 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 時(shí)的測試結(jié)果差不多,UseVectorsX2AArgs 最快,UseVectorsX2BArgs、UseVectorsArgs 并列第2。

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

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

      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 AdvSIMD
        DefaultJob : .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時(shí)的測試結(jié)果為例,來觀察向量化算法比起標(biāo)量算法的性能提升。

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

      • Scalar:1,504.47/60.78 ≈ 24.75。即 .NET 8.0向量算法的性能,是 .NET 7.0標(biāo)量算法的 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。

      此時(shí) UseVectors、UseVectorsArgs、UseVectorsX2AArgs 的成績相差不大,考慮到測試誤差,可以將它們并列第一。這是因?yàn)槭褂肁dvSimd的“多向量換位”指令之后,無論是“2向量換位”,還是“3向量換位”,都是僅需1條換位指令。

      而 UseVectorsX2BArgs 的比 UseVectorsArgs 還慢了。看來對于 Arm架構(gòu),2次向量加載的開銷也是頗大的。

      3.3 .NET Framework

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

      Vectors.Instance:       VectorTraits256Base     //
      YShuffleX3Kernel_AcceleratedTypes:      None
      
      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
        [Host]     : .NET Framework 4.8.1 (4.8.9290.0), X64 RyuJIT VectorSize=256
        DefaultJob : .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 等向量函數(shù)反而更慢了,這是因?yàn)?YShuffleX3Kernel 沒有硬件加速。可以看到 “YShuffleX3Kernel_AcceleratedTypes”為“None”。

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

      四、結(jié)語

      對于 Arm架構(gòu),很明顯思路A(UseVectorsX2AArgs)的性能最好。

      而對于 X86架構(gòu),情況有一些復(fù)雜——

      • 不支持Avx512時(shí):思路B(UseVectorsX2BArgs)最快。思路A(UseVectorsX2AArgs)與ImageshopSse并列第2,都比 UseVectorsArgs 快。
      • 支持Avx512時(shí):思路A(UseVectorsX2AArgs)、思路B(UseVectorsX2BArgs)與UseVectorsArgs等辦法,均并列第1。

      考慮到跨平臺時(shí)代碼的維護(hù)成本,選擇維護(hù)唯一一套代碼比較好。此時(shí)推薦 思路A(UseVectorsX2AArgs)。因?yàn)樗诮^大多數(shù)時(shí)候(Arm、X86支持Avx512時(shí))都是排第1,僅在不支持Avx512時(shí)才排到第2。而且它的代碼相對更簡單。

      若追求極致性能,可考慮維護(hù)2套代碼(思路A、思路B),隨后根據(jù)是否支持Avx512來切換。

      附錄

      posted on 2025-03-16 18:05  zyl910  閱讀(103)  評論(0)    收藏  舉報(bào)
      主站蜘蛛池模板: 成人国产精品免费网站| 国产在线98福利播放视频| 淮滨县| 日韩免费码中文在线观看| 亚洲av高清一区二区三| 亚洲高清中文字幕在线看不卡| 777奇米四色成人影视色区| 亚洲久久色成人一二三区| 日韩精品一区二区都可以| 成人国产精品免费网站| 日本伊人色综合网| 亚洲精品入口一区二区乱| 一区二区三区四区精品视频| 国产成人人综合亚洲欧美丁香花| 亚洲国产精品一区二区第一页| 国内少妇人妻偷人精品| av综合网男人的天堂| 久久三级国内外久久三级| 国产不卡在线一区二区| 国产中文字幕在线一区| 国产麻豆精品手机在线观看| 一区二区三区国产亚洲自拍| 高清国产一区二区无遮挡| 99精品国产一区二区三区不卡| 久久精品免视看国产成人| 噜噜久久噜噜久久鬼88| 18禁无遮挡啪啪无码网站破解版| 亚洲中文字幕乱码一区| 熟妇激情一区二区三区| 视频一区视频二区视频三| 综合亚洲网| 亚洲国产成人资源在线| 免费无码一区无码东京热| AV老司机AV天堂| 少妇人妻偷人精品免费| 亚洲成a人片在线观看久| 亚洲欧美成人aⅴ在线| 丝袜a∨在线一区二区三区不卡| 麻豆天美东精91厂制片| 2021亚洲va在线va天堂va国产 | 成人综合人人爽一区二区|