.NET性能優化-為結構體數組使用StructLinq

前言

本系列的主要目的是告訴大家在遇到性能問題時,有哪些方案可以去優化;并不是要求大家一開始就使用這些方案來提升性能。
在之前幾篇文章中,有很多網友就有一些非此即彼的觀念,在實際中,處處都是開發效率和性能之間取舍的藝術。《計算機編程藝術》一書中提到過早優化是萬惡之源,在進行性能優化時,你必須要問自己幾個問題,看需不要進行性能優化。

  • 優化的成本高么?

  • 如果立刻開始優化會帶來什么影響?

  • 因為對任務目標的影響或是興趣等其他原因而關注這個問題?

  • 任務目標影響有多大?

  • 隨著硬件性能提升或者框架版本升級,優化的結果會不會過時?

  • 如果不進行優化或延遲優化的進行會帶來什么負面的影響?

  • 如果不進行優化或延遲優化,相應的時間或成本可以完成什么事情,是否更有價值?

如果評估下來,還是優化的利大于弊,而且在合理的時間范圍內,那么就去做;如果覺得當前應用的QPS不高、用戶體驗也還好、內存和CPU都有空余,那么就放一放,主要放在二八法則中能為你創建80%價值的事情上。但是大家要記住過早優化是萬惡之源不是寫垃圾代碼的借口。

回到正題,在上篇文章《使用結構體替代類》中有寫在緩存和大數據量計算時使用結構體有諸多的好處,最后關于計算性能的例子中,我使用的是簡單的for循環語句,但是在C#中我們使用LINQ多于使用for循環。有小伙伴就問了兩個問題:

  • 平時使用的LINQ對于結構體是值傳遞還是引用傳遞?

  • 如果是值傳遞,那么有沒有辦法改為引用傳遞?達到更好性能?
    針對這兩個問題特意寫一篇回答一下,字數不多,幾分鐘就能閱讀完。

Linq是值傳遞

在.NET平臺上,默認對于值類型的方法傳參都是值傳遞,除非在方法參數上指定ref,才能變為引用傳遞。
同樣,在LINQ實現的WhereSelectTake眾多方法中,也沒有加入ref關鍵字,所以在LINQ中全部都是值傳遞,如果結構體Size大于8byte(當前平臺的指針大小),那么在調用方法時,結構體的速度要慢于引用傳遞的類。
比如我們編寫如下代碼,使用常見的Linq API進行數據的結構化查詢,分別使用結構體和類,看看效果,數組數據量為1w。

public class SomeClass  
{  public int Value1; public int Value2;  public float Value3; public double Value4;  public string? Value5; public decimal Value6;  public DateTime Value7; public TimeOnly Value8;  public DateOnly Value9;  
}  public struct SomeStruct  
{  public int Value1; public int Value2;  public float Value3; public double Value4;  public string? Value5; public decimal Value6;  public DateTime Value7; public TimeOnly Value8;  public DateOnly Value9;
}[MemoryDiagnoser]  
[Orderer(SummaryOrderPolicy.FastestToSlowest)]  
public class Benchmark  
{  private static readonly SomeClass[] ClassArray;  private static readonly SomeStruct[] StructArray;  static Benchmark()  {  var baseTime = DateTime.Now;  ClassArray = new SomeClass[10000];  StructArray = new SomeStruct[10000];  for (int i = 0; i < 10000; i++)  {  var item = new SomeStruct  {  Value1 = i, Value2 = i, Value3 = i,  Value4 = i, Value5 = i.ToString(),  Value6 = i, Value7 = baseTime.AddHours(i),  Value8 = TimeOnly.MinValue, Value9 = DateOnly.MaxValue  };  StructArray[i] = item;  ClassArray[i] = new SomeClass  {  Value1 = i, Value2 = i, Value3 = i,  Value4 = i, Value5 = i.ToString(),  Value6 = i, Value7 = baseTime.AddHours(i),  Value8 = TimeOnly.MinValue, Value9 = DateOnly.MaxValue  };  }  }  [Benchmark(Baseline = true)]  public decimal Class()  {  return ClassArray.Where(x => x.Value1 > 5000)  .Where(x => x.Value3 > 5000)  .Where(x => x.Value7 > DateTime.MinValue)  .Where(x => x.Value5 != string.Empty)  .Where(x => x.Value6 > 1)  .Where(x => x.Value8 > TimeOnly.MinValue)  .Where(x => x.Value9 > DateOnly.MinValue)  .Skip(100)  .Take(10000)  .Select(x => x.Value6)  .Sum();  }  [Benchmark]  public decimal Struct()  {  return StructArray.Where(x => x.Value1 > 5000)  .Where(x => x.Value3 > 5000)  .Where(x => x.Value7 > DateTime.MinValue)  .Where(x => x.Value5 != string.Empty)  .Where(x => x.Value6 > 1)  .Where(x => x.Value8 > TimeOnly.MinValue)  .Where(x => x.Value9 > DateOnly.MinValue)  .Skip(100)  .Take(10000)  .Select(x => x.Value6)  .Sum();  }  
}

Benchmakr的結果如下,大家看到在速度上有5倍的差距,結構體由于頻繁裝箱內存分配的也更多。
b703a4a2e022e7aae875080c8a4957a2.png
那么注定沒辦開開心心的在結構體上用LINQ了嗎?那當然不是,引入我們今天要給大家介紹的項目。

使用StructLinq

首先來介紹一下StructLinq,在C#中用結構體實現LINQ,以大幅減少內存分配并提高性能。引入IRefStructEnumerable,以提高元素為胖結構體(胖結構體是指結構體大小大于16Byte)時的性能。

引入StructLinq

這個庫已經分發在?NuGet上。可以直接通過下面的命令安裝?StructLinq?:

PM> Install-Package StructLinq

簡單使用

下方就是一個簡單的使用,用來求元素和。唯一不同的地方就是需要調用ToStructEnumerable方法。

using StructLinq;int[] array = new [] {1, 2, 3, 4, 5};int result = array.ToStructEnumerable().Where(x => (x & 1) == 0, x=>x).Select(x => x *2, x => x).Sum();

x=>x用于避免裝箱(和分配內存),并幫助泛型參數推斷。你也可以通過對WhereSelect函數使用結構來提高性能。

性能

所有的跑分結果你可以在這里找到. 舉一個例子,下方代碼的Linq查詢:

list.Where(x => (x & 1) == 0).Select(x => x * 2).Sum();

可以被替換為下面的代碼:

list.ToStructEnumerable().Where(x => (x & 1) == 0).Select(x => x * 2).Sum();

或者你想零分配內存,可以像下面一樣寫(類型推斷出來,沒有裝箱):

list.ToStructEnumerable().Where(x => (x & 1) == 0, x=>x).Select(x => x * 2, x=>x).Sum(x=>x);

如果想要零分配和更好的性能,可以像下面一樣寫:

var where = new WherePredicate();var select = new SelectFunction();list.ToStructEnumerable().Where(ref @where, x => x).Select(ref @select, x => x, x => x).Sum(x => x);

上方各個代碼的Benchmark結果如下所示:

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042
Intel Core i7-8750H CPU 2.20GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET Core SDK=5.0.101[Host]     : .NET Core 5.0.1 (CoreCLR 5.0.120.57516, CoreFX 5.0.120.57516), X64 RyuJITDefaultJob : .NET Core 5.0.1 (CoreCLR 5.0.120.57516, CoreFX 5.0.120.57516), X64 RyuJIT
MethodMeanErrorStdDevRatioGen 0Gen 1Gen 2Allocated
LINQ65.116 μs0.6153 μs0.5756 μs1.00---152 B
StructLinqWithDelegate26.146 μs0.2402 μs0.2247 μs0.40---96 B
StructLinqWithDelegateZeroAlloc27.854 μs0.0938 μs0.0783 μs0.43----
StructLinqZeroAlloc6.872 μs0.0155 μs0.0137 μs0.11----

StructLinq在這些場景里比默認的LINQ實現快很多。

在上文場景中使用

我們也把上面的示例代碼使用StructLinq改寫一下。

// 引用類型使用StructLinq
[Benchmark]  
public double ClassStructLinq()  
{  return ClassArray  .ToStructEnumerable()  .Where(x => x.Value1 > 5000)  .Where(x => x.Value3 > 5000)  .Where(x => x.Value7 > DateTime.MinValue)  .Where(x => x.Value5 != string.Empty)  .Where(x => x.Value6 > 1)  .Where(x => x.Value8 > TimeOnly.MinValue)  .Where(x => x.Value9 > DateOnly.MinValue)  .Skip(100)  .Take(10000)  .Select(x => x.Value4)  .Sum(x => x);  
}  // 結構體類型使用StructLinq
[Benchmark]  
public double StructLinq()  
{  return StructArray  .ToStructEnumerable()  .Where(x => x.Value1 > 5000)  .Where(x => x.Value3 > 5000)  .Where(x => x.Value7 > DateTime.MinValue)  .Where(x => x.Value5 != string.Empty)  .Where(x => x.Value6 > 1)  .Where(x => x.Value8 > TimeOnly.MinValue)  .Where(x => x.Value9 > DateOnly.MinValue)  .Skip(100)  .Take(10000)  .Select(x => x.Value4)  .Sum(x => x);  
}  // 結構體類型 StructLinq 零分配
[Benchmark]  
public double StructLinqZeroAlloc()  
{  return StructArray  .ToStructEnumerable()  .Where(x => x.Value1 > 5000, x=> x)  .Where(x => x.Value3 > 5000, x => x)  .Where(x => x.Value7 > DateTime.MinValue, x => x)  .Where(x => x.Value5 != string.Empty, x => x)  .Where(x => x.Value6 > 1, x => x)  .Where(x => x.Value8 > TimeOnly.MinValue, x => x)  .Where(x => x.Value9 > DateOnly.MinValue, x => x)  .Skip(100)  .Take(10000)  .Select(x => x.Value4, x => x)  .Sum(x => x);  
}  // 結構體類型 StructLinq 引用傳遞
[Benchmark]  
public double StructLinqRef()  
{  return StructArray  .ToRefStructEnumerable()  // 這里使用的是ToRefStructEnumerable.Where((in SomeStruct x) => x.Value1 > 5000)  .Where((in SomeStruct x) => x.Value3 > 5000)  .Where((in SomeStruct x) => x.Value7 > DateTime.MinValue)  .Where((in SomeStruct x) => x.Value5 != string.Empty)  .Where((in SomeStruct x) => x.Value6 > 1)  .Where((in SomeStruct x) => x.Value8 > TimeOnly.MinValue)  .Where((in SomeStruct x) => x.Value9 > DateOnly.MinValue)  .Skip(100)  .Take(10000)  .Select((in SomeStruct x) => x.Value4)  .Sum(x => x);  
}  // 結構體類型 StructLinq 引用傳遞 零分配
[Benchmark]  
public double StructLinqRefZeroAlloc()  
{  return StructArray  .ToRefStructEnumerable()  .Where((in SomeStruct x) => x.Value1 > 5000, x=> x)  .Where((in SomeStruct x) => x.Value3 > 5000, x=> x)  .Where((in SomeStruct x) => x.Value7 > DateTime.MinValue, x=> x)  .Where((in SomeStruct x) => x.Value5 != string.Empty, x=> x)  .Where((in SomeStruct x) => x.Value6 > 1, x => x)  .Where((in SomeStruct x) => x.Value8 > TimeOnly.MinValue, x=> x)  .Where((in SomeStruct x) => x.Value9 > DateOnly.MinValue, x=> x)  .Skip(100, x => x)  .Take(10000, x => x)  .Select((in SomeStruct x) => x.Value4, x=> x)  .Sum(x => x, x=>x);  
}  // 結構體 直接for循環
[Benchmark]  
public double StructFor()  
{  double sum = 0;  int skip = 100;  int take = 10000;  for (int i = 0; i < StructArray.Length; i++)  {  ref var x = ref StructArray[i];  if(x.Value1 <= 5000) continue;  if(x.Value3 <= 5000) continue;  if(x.Value7 <= DateTime.MinValue) continue;  if(x.Value5 == string.Empty) continue;  if(x.Value6 <= 1) continue;  if(x.Value8 <= TimeOnly.MinValue) continue;  if(x.Value9 <= DateOnly.MinValue) continue;  if(i < skip) continue;  if(i >= skip + take) break;  sum += x.Value4;  }  return sum;  
}

最后的Benchmark結果如下所示。
f8bfaab4fd0d3898e6b576cef36b8482.png
從以上Benchmark結果可以得出以下結論:

  • 類和結構體都可以使用StructLinq來減少內存分配。

  • 類和結構體使用StructLinq都會導致代碼跑的更慢。

  • 結構體類型使用StructLinq的引用傳遞模式可以獲得5倍的性能提升,比引用類型更快。

  • 無論是LINQ還是StructLinq由于本身的復雜性,性能都沒有For循環來得快。

總結

在已經用上結構體的高性能場景,其實不建議使用LINQ了,因為LINQ本身它性能就存在瓶頸,它主要就是為了提升開發效率。建議直接使用普通循環。
如果一定要使用,那么建議大于8byte的結構體使用StructLinq的引用傳遞模式(ToRefStructEnumerable),這樣可以把普通LINQ結構體的性能提升5倍以上,也能幾乎不分配額外的空間。

作者:InCerry

出處:https://www.cnblogs.com/InCerry/p/Dotnet-Perf-Opt-Use-StructLinq-For-ValueType.html

版權:本作品采用「署名-非商業性使用-相同方式共享 4.0 國際」許可協議進行許可。

聲明:本博客版權歸「InCerry」所有。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/287699.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/287699.shtml
英文地址,請注明出處:http://en.pswp.cn/news/287699.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Android Studio之提示Unable to delete directory ‘*****\MyApplication\app\build‘

1 問題 運行android studio 無論clean project還是rebuild project,提示如下 Unable to delete directory *****\MyApplication\app\buildFailed to delete some children. This might happen because a process has files open or has its working directory set in the tar…

《假如編程是魔法之零基礎看得懂的Python入門教程 》——(七)我把魔法變成了積木

學習目標 了解魔法積木的使用——自定義函數了解魔法積木的結果反饋——自定義函數返回值了解魔法積木的原料傳遞——自定義函數傳參了解魔法積木的類型分類——類與對象 推薦 1.《備受好評的看得懂的C語言入門教程》 目錄 第一篇&#xff1a;《假如編程是魔法之零基礎看得…

關于Activity的getReferrer():如何在Activity中獲取調用者?

http://blog.csdn.net/u013553529/article/details/53856800 關于Activity的getReferrer()之一&#xff1a;如何在Activity中獲取調用者&#xff1f; http://blog.csdn.net/u013553529/article/details/53882440 關于Activity的getReferrer()之二&#xff1a;調用者的包名是如何…

java之七 高級類設計

static的用法 有時你希望定義一個類成員&#xff0c;使它的使用完全獨立于該類的任何對象。通常情況下&#xff0c;類成員必須通過它的類的對象訪問&#xff0c;但是可以創建這樣一個成員&#xff0c;它能夠被它自己使用&#xff0c;而不必引用特定的實例。在成員的聲明前面加上…

【遙感數字圖像處理】實驗:Erdas 軟件的認識與使用

Erdas軟件下載地址:《GISer福音來了:測繪地理信息類專業軟件版本大全匯總下載!》 1.1 簡介 ERDAS IMAGINE 是美國 ERDAS(Earth Resource Data Analysis System)公司開發的遙感圖像處理系統,它以其先進的圖像處理技術,友好、靈活的用戶界面和操作方式,面向廣闊應用領域…

import 別名_Python基礎找茬系列09--import和from-import的引用區別

一、語法區別二、兩種導包在內存上的區別一圖看懂import與from-import的區別無論是使用import隱式導包還是form-import顯示導包&#xff0c;整個模塊都會被加載到內存中例如&#xff1a;from test import b,整個模塊依舊進入內存&#xff0c;因為如果只有函數b進入內存&#xf…

Android Studio之提示Gradle sync failed: Plugin with id ‘com.novoda.bintray-release‘ not found.

1 問題 導入別人的模塊到Android Studio,錯誤提示如下 Gradle sync failed: Plugin with id com.novoda.bintray-release not found. 2 解決辦法 在project的build.gradle里面添加如下 dependencies {classpath com.android.tools.build:gradle:3.5.2//加上下面的代碼classp…

設計一個限速器

限速器 (Rate Limiter) 相信大家都不會陌生&#xff0c;在網絡系統中&#xff0c;限速器可以控制客戶端發送流量的速度&#xff0c;比如 TCP, QUIC 等協議。而在 HTTP 的世界中&#xff0c; 限速器可以限制客戶端在一段時間內發送請求的次數&#xff0c;如果超過設定的閾值&…

C語言新手的100個報錯解法 已更新11個錯誤

學習目標 收藏文章報錯可以過來查 [更新數據] 此文將會持續更新&#xff0c;收錄錯誤信息&#xff0c;若本文沒有收錄記得聯系我~ CSDN 1_bit 持續更新中… [發布日期&#xff1a;2020年11月16日 14:55:00] 更新&#xff1a; 暫無 C語言教程 C語言真的很難嗎&#xff1f;那…

【遙感數字圖像處理】實驗:遙感圖像顯示與數據輸入/輸出(Erdas版)

一、實驗平臺&#xff1a;Erdas 9.1 二、實驗內容&#xff1a;視窗功能簡介、圖形和圖像顯示操作、實用菜單操作、顯示操作、AOI菜單操作、矢量和柵格菜單、數據的輸入輸出等。 三、實驗目的&#xff1a;初步了解Erdas的主要功能模塊&#xff0c;在此基礎上&#xff0c;掌握視…

在Windows Server2016中安裝SQL Server2016(轉)

在Windows Server2016中安裝SQL Server2016&#xff08;轉&#xff09; 轉自&#xff1a; http://blog.csdn.net/yenange/article/details/52980135 參考&#xff1a; SQL Server2016企業版 附全版本key - moonpure的專欄 - CSDN博客 http://blog.csdn.net/moonpure/article/d…

mysql的復雜查詢_mysql復雜查詢

所謂復雜查詢&#xff0c;指涉及多個表、具有嵌套等復雜結構的查詢。這里簡要介紹典型的幾種復雜查詢格式。一、連接查詢連接是區別關系與非關系系統的最重要的標志。通過連接運算符可以實現多個表查詢。連接查詢主要包括內連接、外連接等。假設有Student和Grade兩個表如下&…

數據庫調優要點紀要

數據庫瓶頸一般在IO和CPU 1、少用group by, order by 2、通過索引來排序&#xff08;不要所有字段都用索引&#xff0c;因為insert、update要重構索引很耗時&#xff09; 3、避免select * 4、少用join 5、join和子查詢&#xff0c;還是用join來代替子查詢吧 6、少用or 7、用uni…

Unity3D 之UGUI 滑動條(Slider)

這里來講解下UGUI 滑動條(Slider)的用法 控件下面有三個游戲對象 Background -->背景 Fill Area --> 前景區域 Handle Slide Area --> 滑動條 Slider的屬性 其他幾個設置和其他控件都差不多&#xff0c;這里來講解幾個特有的屬性。 Direction -->方向 Whole Number…

Android Studio導入別人的module提示錯誤Plugin with id ‘com.jfrog.bintray‘ not found.

1 問題 Android Studio導入別人的module提示錯誤如下 Plugin with id com.jfrog.bintray not found. Plugin with id com.github.dcendents.android-maven not found 2 解決辦法 在我們的項目的build.gradle添加如下配置 buildscript {repositories {google()jcenter()}dep…

C語言真的很難嗎?那是你沒看這張圖,化整為零輕松學習C語言。

真不難 C語言難不難&#xff1f;這個問題是相對的&#xff0c;對于找到合適方法學習C語言的同學想必是覺得很簡單&#xff1b;但對于一部分同學來說&#xff0c;沒有眾觀全局就會誤以為剛入門就需要學習龐大的知識&#xff0c;學著學著開始看不懂&#xff0c;由于心理作怪&…

【中間件】.net Core中使用HttpReports進行接口統計,分析, 可視化, 監控,追蹤等...

HttpReports 基于.Net Core 開發的APM監控系統&#xff0c;使用MIT開源協議&#xff0c;主要功能包括&#xff0c;統計, 分析, 可視化&#xff0c; 監控&#xff0c;追蹤等&#xff0c;適合在微服務環境中使用。官方地址&#xff1a;https://www.yuque.com/httpreports/docs/u…

【遙感數字圖像處理】實驗:遙感影像輻射糾正(大氣糾正)完整操作圖文教程(Erdas版)

一、實驗平臺:Erdas 9.1 二、實驗數據:dmtm.img 三、實驗內容:利用回歸分析法校正影像 四、實驗原理:大氣散射只影響短波波段,長短波進行對比,找出影響短波的程輻射值,將其減去 五、實驗目的:掌握回歸分析法校正影像的方法及步驟,能熟練地對影像進行校正 六、實…

Android之開源視頻壓縮框架RxFFmpeg的commands設置

1 Android視頻壓縮框架 地址:https://github.com/microshow/RxFFmpeg 2 問題 用ffmpeg進行壓縮的時候,我們需要采用ffmpeg命令壓縮官網給的命令如下 String text = "ffmpeg -y -i /storage/emulated/0/1/input.mp4 -vf boxblur=25:5 -preset superfast /storage/emul…

Acitivty生命周期

為什么80%的碼農都做不了架構師&#xff1f;>>> Acitivty 有七個生命周期&#xff1a; onCreate&#xff1a;當第一次調用一個Activity就會執行onCreate方法 onStart&#xff1a;當Activity處于可見狀態的時候就會調用onStart方法 onResume&#xff1a;當Activity可…