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

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

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

      是什么讓.NET7的Min和Max方法性能暴增了45倍?

      簡介

      在之前的一篇文章.NET性能系列文章一:.NET7的性能改進中我們聊到Linq中的Min()Max()方法.NET7比.NET6有高達45倍的性能提升,當時Benchmark代碼和結(jié)果如下所示:

      [Params(1000)]
      public int Length { get; set; }
      
      private int[] arr;
      
      [GlobalSetup]
      public void GlobalSetup() => arr = Enumerable.Range(0, Length).ToArray();
      
      [Benchmark]
      public int Min() => arr.Min();
      
      [Benchmark]
      public int Max() => arr.Max();
      
      方法 運行時 數(shù)組長度 平均值 比率 分配
      Min 1000 3,494.08 ns 53.24 32 B
      Min 1000 65.64 ns 1.00 -
      Max 1000 3,025.41 ns 45.92 32 B
      Max 1000 65.93 ns 1.00 -

      可以看到有高達45倍的性能提升,那就有小伙伴比較疑惑,在.NET7中到底是做了什么讓它有如此大的性能提升?
      所以本文就通過.NET7中的一些pr帶大家一起探索下.NET7的Min()Max()方法是如何變快的。

      探索

      首先我們打開.NET Runtime的倉庫,應該沒有人不會知道倉庫的地址吧?里面包含了.NET運行時所有的代碼,包括CLR和BCL庫。地址如下所示:
      https://github.com/dotnet/runtime

      然后我們熟練的根據(jù)命名空間System.Linq找到Linq所在的文件夾位置,如下所示:

      可以看到很多Linq相關(guān)的方法都在這個文件夾內(nèi),讓我們先來找一找Max()方法所對應的類。就是下方所示,我們可以看到剛好異步小王子Stephen Toub大佬提交了一個優(yōu)化代碼。

      然后我們點擊History查看這個類的提交歷史,我們發(fā)現(xiàn)Stephen大佬在今年多次提交代碼,都是優(yōu)化其性能。

      找到Stephen大佬的第一個提交,我們發(fā)現(xiàn)在Max的代碼中,多了一個特殊的路徑,如果數(shù)據(jù)類型為int[],那么就走單獨的一個方法重載,并在這個重載中啟用了SIMD向量化,代碼如下所示:

      SIMD向量化在我之前的多篇文章中都有提到(如:.NET如何快速比較兩個byte數(shù)組是否相等),它是CPU的特殊指令,使用它可以大幅度的增強運算性能,我猜這就是性能提升的原因。

      我們可以看到在上面只為int[]做了優(yōu)化,然后繼續(xù)瀏覽了Stephen大佬的其它幾個PR,Stephen大佬將代碼抽象了一下,使用了泛型的特性,然后順便為其它的基本值類型都做了優(yōu)化。能享受到性能提升的有byte sbyte ushort short uint int ulong long nuint nint

      所以我們以最后一個提交為例,看看到底是用了什么SIMD指令,什么樣的方法來提升的性能。抽取出來的核心代碼如下所示:

      private static T MinMaxInteger<T, TMinMax>(this IEnumerable<T> source)
          where T : struct, IBinaryInteger<T>
          where TMinMax : IMinMaxCalc<T>
      {
          T value;
      
          if (source.TryGetSpan(out ReadOnlySpan<T> span))
          {
              if (span.IsEmpty)
              {
                  ThrowHelper.ThrowNoElementsException();
              }
      
              // 判斷當前平臺是否支持使用Vector-128 或者 總數(shù)據(jù)長度是否小于128位
              // Vector128是指硬件支持同時計算128位二進制數(shù)據(jù)
              if (!Vector128.IsHardwareAccelerated || span.Length < Vector128<T>.Count)
              {
                  // 進入到此路徑,說明最基礎的Vector128都不支持,那么直接使用for循環(huán)來比較
                  value = span[0];
                  for (int i = 1; i < span.Length; i++)
                  {
                      if (TMinMax.Compare(span[i], value))
                      {
                          value = span[i];
                      }
                  }
              }
              // 判斷當前平臺是否支持使用Vector-256 或者 總數(shù)據(jù)長度是否小于256位
              // Vector256是指硬件支持同時計算256位二進制數(shù)據(jù)
              else if (!Vector256.IsHardwareAccelerated || span.Length < Vector256<T>.Count)
              {
                  // 進入到此路徑,說明支持Vector128但不支持Vector256
                  // 那么進入128位的向量化的比較
      
                  // 獲取當前數(shù)組的首地址,也就是指向第0個元素
                  ref T current = ref MemoryMarshal.GetReference(span);
                  // 獲取Vector128能使用的最后地址,因為整個數(shù)組占用的bit位有可能不能被128整除
                  // 也就是說最后的尾巴不夠128位讓CPU跑一次,那么就直接最后往前數(shù)128位,讓CPU能完整的跑完
                  ref T lastVectorStart = ref Unsafe.Add(ref current, span.Length - Vector128<T>.Count);
      
                  // 從內(nèi)存首地址加載0-127bit數(shù)據(jù),作為最大值的基準
                  Vector128<T> best = Vector128.LoadUnsafe(ref current);
                  // 計算下一個的位置,也就是偏移128位
                  current = ref Unsafe.Add(ref current, Vector128<T>.Count);
                  // 循環(huán)比較 確保地址小于最后地址
                  while (Unsafe.IsAddressLessThan(ref current, ref lastVectorStart))
                  {
                      // 此時TMinMax.Compare重載代碼 => Vector128.Max(left, right);
                      // Vector128.Max 會根據(jù)類型一一比較,每x位最大的返回,
                      // 比如int就是每32位比較,詳情可以看我后文的解析
                      best = TMinMax.Compare(best, Vector128.LoadUnsafe(ref current));
                      current = ref Unsafe.Add(ref current, Vector128<T>.Count);
                  }
                  // 最后一組Vector128進行比較
                  best = TMinMax.Compare(best, Vector128.LoadUnsafe(ref lastVectorStart));
      
                  // 由于Vector128最后的結(jié)果是128位,比如我們類型是int32,那么最后的結(jié)果就有
                  // 4個int32元素,我們還需要從這4個int32元素中找到最大的
                  value = best[0];
                  for (int i = 1; i < Vector128<T>.Count; i++)
                  {
                      // 這里 TMinMax.Compare就是簡單的大小于比較
                      // left > right
                      if (TMinMax.Compare(best[i], value))
                      {
                          value = best[i];
                      }
                  }
              }
              else
              {
                  // Vector256執(zhí)行流程和Vector128一致
                  // 只是它能一次性判斷256位,舉個例子就是一個指令8個int32
                  ref T current = ref MemoryMarshal.GetReference(span);
                  ref T lastVectorStart = ref Unsafe.Add(ref current, span.Length - Vector256<T>.Count);
      
                  Vector256<T> best = Vector256.LoadUnsafe(ref current);
                  current = ref Unsafe.Add(ref current, Vector256<T>.Count);
      
                  while (Unsafe.IsAddressLessThan(ref current, ref lastVectorStart))
                  {
                      best = TMinMax.Compare(best, Vector256.LoadUnsafe(ref current));
                      current = ref Unsafe.Add(ref current, Vector256<T>.Count);
                  }
                  best = TMinMax.Compare(best, Vector256.LoadUnsafe(ref lastVectorStart));
      
                  value = best[0];
                  for (int i = 1; i < Vector256<T>.Count; i++)
                  {
                      if (TMinMax.Compare(best[i], value))
                      {
                          value = best[i];
                      }
                  }
              }
          }
          else
          {
              // 如果不是基本類型的數(shù)組,那么進入迭代器,使用原始方法比較
              using (IEnumerator<T> e = source.GetEnumerator())
              {
                  if (!e.MoveNext())
                  {
                      ThrowHelper.ThrowNoElementsException();
                  }
      
                  value = e.Current;
                  while (e.MoveNext())
                  {
                      T x = e.Current;
                      if (TMinMax.Compare(x, value))
                      {
                          value = x;
                      }
                  }
              }
          }
      
          return value;
      }
      

      以上就是代碼的解析,相信很多人疑惑的地方就是Vector128.Max做了什么,我們可以構(gòu)造一個代碼,讓大家簡單的看出來發(fā)生了什么。代碼和運行結(jié)果如下所示:

      // 定義一個數(shù)組
      var array = new int[] { 4, 3, 2, 1, 1, 2, 3, 4 };
      
      // 拿到數(shù)組首地址指針
      ref int current = ref MemoryMarshal.GetReference(array.AsSpan());
      
      // 從首地址加載128位數(shù)據(jù),上面是int32
      // 所以x = 4, 3, 2, 1
      var x = Vector128.LoadUnsafe(ref current);
      
      // 偏移128位以后,繼續(xù)加載128位數(shù)據(jù)
      // 所以y = 1, 2, 3, 4
      var y = Vector128.LoadUnsafe(ref Unsafe.Add(ref current, Vector128<int>.Count));
      
      // 使用Vector128.Max進行計算
      var result = Vector128.Max(x, y);
      
      // 打印輸出結(jié)果
      x.Dump();
      y.Dump();
      result.Dump();
      


      從運行的結(jié)果可以看到,result中保存的是xy對應位置的最大值,這樣是不是就覺得清晰明了,Stephe大佬上文的代碼就是做了這樣一個操作。

      同樣,如果我們把int32換成int64,也就是long類型,由于一個元素占用64位,所以一次只能加載2個int64元素比較最大值,得出對應位置的最大值:

      最后使用下面的for循環(huán)代碼,從result中找到最大的那個int32元素,從我們上文的案例中就是4,結(jié)果和代碼如下所示:

      var value = result[0];
      for (int i = 1; i < Vector128<int>.Count; i++)
      {
      	if (value < result[i])
      	{
      		value = result[i];
      	}
      }
      


      要注意的是,為了演示方便我這里數(shù)組bit長度剛好是128倍數(shù),實際情況中需要考慮不是128倍數(shù)的場景。

      總結(jié)

      答案顯而易見,試.NET7中Min()Max()方法性能暴增45倍的原因就是Stephe大佬對基本幾個連續(xù)的值類型比較做了SIMD優(yōu)化,而這樣的優(yōu)化在本次的.NET7版本中有非常多,后面有時間帶大家一起看看SIMD又是如何提升其它方面的性能的。

      posted @ 2022-10-28 09:23  InCerry  閱讀(8936)  評論(18)    收藏  舉報
      主站蜘蛛池模板: 人妻中文字幕精品一页| 成年视频人免费网站动漫在线| 久九九精品免费视频| av一区二区中文字幕| 日日碰狠狠添天天爽五月婷| 日本视频高清一区二区三区| 人人妻人人妻人人片色av| 亚洲av永久无码精品水牛影视| 国内少妇人妻丰满av| av亚洲在线一区二区| 美女禁区a级全片免费观看| 88国产精品视频一区二区三区| 国产一区二区三区乱码在线观看| 日本高清一区免费中文视频| 日本免费一区二区三区| 色综合天天综合网天天看片| 亚洲一区成人av在线| 精品乱码一区二区三四区视频| 亚洲第一二三区日韩国产| 亚洲av激情久久精品人| 久久99精品国产99久久6尤物| 色成人亚洲| 自拍视频亚洲精品在线| 9丨精品国产高清自在线看| 久久男人av资源站| 欧洲熟妇色自偷自拍另类| 一色屋精品视频在线观看| 亚洲成在人天堂一区二区| 久久精品人人槡人妻人人玩AV | 免费午夜无码片在线观看影院| 国产成人亚洲精品成人区| jk白丝喷浆| 伊人久久大香线蕉AV网| 亚洲精品无码久久一线| 欧美激烈精交gif动态图| 亚洲精品男男一区二区| 亚洲精品久久久久国色天香| 97色伦97色伦国产| 推特国产午夜福利在线观看| 亚洲精品色一区二区三区| 免费国产午夜理论片不卡|