最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C#中的in參數(shù)與性能分析詳解

 更新時間:2020年11月02日 12:06:50   作者:技術譯站  
這篇文章主要給大家介紹了關于C#中in參數(shù)與性能分析的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

前言

in 修飾符也是從 C# 7.2 開始引入的,它與我們上一篇中討論的 《C# 中的只讀結構體(readonly struct)》1 是緊密相關的。

in 修飾符

in 修飾符通過引用傳遞參數(shù)。 它讓形參成為實參的別名,即對形參執(zhí)行的任何操作都是對實參執(zhí)行的。 它類似于 ref 或 out 關鍵字,不同之處在于 in 參數(shù)無法通過調用的方法進行修改。

  • ref 修飾符,指定參數(shù)由引用傳遞,可以由調用方法讀取或寫入。
  • out 修飾符,指定參數(shù)由引用傳遞,必須由調用方法寫入。
  • in 修飾符,指定參數(shù)由引用傳遞,可以由調用方法讀取,但不可以寫入。

舉個簡單的例子:

struct Product
{
 public int ProductId { get; set; }
 public string ProductName { get; set; }
}

public static void Modify(in Product product)
{
 //product = new Product();   // 錯誤 CS8331 無法分配到 變量 'in Product',因為它是只讀變量
 //product.ProductName = "測試商品"; // 錯誤 CS8332 不能分配到 變量 'in Product' 的成員,因為它是只讀變量
 Console.WriteLine($"Id: {product.ProductId}, Name: {product.ProductName}"); // OK
}

引入 in 參數(shù)的原因

我們知道,結構體實例的內存在棧(stack)上進行分配,所占用的內存隨聲明它的類型或方法一起回收,所以通常在內存分配上它是比引用類型占有優(yōu)勢的。2

但是對于有些很大(比如有很多字段或屬性)的結構體,將其作為方法參數(shù),在緊湊的循環(huán)或關鍵代碼路徑中調用方法時,復制這些結構的成本就會很高。當所調用的方法不修改該參數(shù)的狀態(tài),使用新的修飾符 in 聲明參數(shù)以指定此參數(shù)可以按引用安全傳遞,可以避免(可能產生的)高昂的復制成本,從而提高代碼運行的性能。

in 參數(shù)對性能的提升

為了測試 in 修飾符對性能的提升,我定義了兩個較大的結構體,一個是可變的結構體 NormalStruct,一個是只讀的結構體 ReadOnlyStruct,都定義了 30 個屬性,然后定義三個測試方法:

  • DoNormalLoop 方法,參數(shù)不加修飾符,傳入一般結構體,這是以前比較常見的做法。
  • DoNormalLoopByIn 方法,參數(shù)加 in 修飾符,傳入一般結構體。
  • DoReadOnlyLoopByIn 方法,參數(shù)加 in 修飾符,傳入只讀結構體。

代碼如下所示:

public struct NormalStruct
{
 public decimal Number1 { get; set; }
 public decimal Number2 { get; set; }
 //...
 public decimal Number30 { get; set; }
}

public readonly struct ReadOnlyStruct
{
 public readonly decimal Number1 { get; }
 public readonly decimal Number2 { get; }
 //...
 public readonly decimal Number30 { get; }
}

public class BenchmarkClass
{
 const int loops = 50000000;
 NormalStruct normalInstance = new NormalStruct();
 ReadOnlyStruct readOnlyInstance = new ReadOnlyStruct();

 [Benchmark(Baseline = true)]
 public decimal DoNormalLoop()
 {
  decimal result = 0M;
  for (int i = 0; i < loops; i++)
  {
   result = Compute(normalInstance);
  }
  return result;
 }

 [Benchmark]
 public decimal DoNormalLoopByIn()
 {
  decimal result = 0M;
  for (int i = 0; i < loops; i++)
  {
   result = ComputeIn(in normalInstance);
  }
  return result;
 }

 [Benchmark]
 public decimal DoReadOnlyLoopByIn()
 {
  decimal result = 0M;
  for (int i = 0; i < loops; i++)
  {
   result = ComputeIn(in readOnlyInstance);
  }
  return result;
 }

 public decimal Compute(NormalStruct s)
 {
  //業(yè)務邏輯
  return 0M;
 }

 public decimal ComputeIn(in NormalStruct s)
 {
  //業(yè)務邏輯
  return 0M;
 }

 public decimal ComputeIn(in ReadOnlyStruct s)
 {
  //業(yè)務邏輯
  return 0M;
 }
}

在沒有使用 in 參數(shù)的方法中,意味著每次調用傳入的是變量的一個新副本; 而在使用 in 修飾符的方法中,每次不是傳遞變量的新副本,而是傳遞同一副本的只讀引用。

使用 BenchmarkDotNet 工具測試三個方法的運行時間,結果如下:

|             Method |       Mean |    Error |    StdDev |     Median | Ratio | RatioSD |
|------------------- |-----------:|---------:|----------:|-----------:|------:|--------:|
|       DoNormalLoop | 1,536.3 ms | 65.07 ms | 191.86 ms | 1,425.7 ms |  1.00 |    0.00 |
|   DoNormalLoopByIn |   480.9 ms | 27.05 ms |  79.32 ms |   446.3 ms |  0.32 |    0.07 |
| DoReadOnlyLoopByIn |   581.9 ms | 35.71 ms | 105.30 ms |   594.1 ms |  0.39 |    0.10 |

從這個結果可以看出,如果使用 in 參數(shù),不管是一般的結構體還是只讀結構體,相對于不用 in 修飾符的參數(shù),性能都有較大的提升。這個性能差異在不同的機器上運行可能會有所不同,但是毫無疑問,使用 in 參數(shù)會得到更好的性能。

在 Parallel.For 中使用

在上面簡單的 for 循環(huán)中,我們看到 in 參數(shù)有助于性能的提升,那么在并行運算中呢?我們把上面的 for 循環(huán)改成使用 Parallel.For 來實現(xiàn),代碼如下:

[Benchmark(Baseline = true)]
public decimal DoNormalLoop()
{
 decimal result = 0M;
 Parallel.For(0, loops, i => Compute(normalInstance));
 return result;
}

[Benchmark]
public decimal DoNormalLoopByIn()
{
 decimal result = 0M;
 Parallel.For(0, loops, i => ComputeIn(in normalInstance));
 return result;
}

[Benchmark]
public decimal DoReadOnlyLoopByIn()
{
 decimal result = 0M;
 Parallel.For(0, loops, i => ComputeIn(in readOnlyInstance));
 return result;
}

事實上,道理是一樣的,在使用 in 參數(shù)的方法中,每次調用傳入的是變量的一個新副本; 在使用 in 修飾符的方法中,每次傳遞的是同一副本的只讀引用。

使用 BenchmarkDotNet 工具測試三個方法的運行時間,結果如下:

|             Method |     Mean |    Error |   StdDev | Ratio |
|------------------- |---------:|---------:|---------:|------:|
|       DoNormalLoop | 793.4 ms | 13.02 ms | 11.54 ms |  1.00 |
|   DoNormalLoopByIn | 352.4 ms |  6.99 ms | 17.27 ms |  0.42 |
| DoReadOnlyLoopByIn | 341.1 ms |  6.69 ms | 10.02 ms |  0.43 |

同樣表明,使用 in 參數(shù)會得到更好的性能。

使用 in 參數(shù)需要注意的地方

我們來看一個例子,定義一個一般的結構體,包含一個屬性 Value 和 一個修改該屬性的方法 UpdateValue。 然后在別的地方也定義一個方法 UpdateMyNormalStruct 來修改該結構體的屬性 Value。 代碼如下:

struct MyNormalStruct
{
 public int Value { get; set; }

 public void UpdateValue(int value)
 {
  Value = value;
 }
}

class Program
{
 static void UpdateMyNormalStruct(MyNormalStruct myStruct)
 {
  myStruct.UpdateValue(8);
 }

 static void Main(string[] args)
 {
  MyNormalStruct myStruct = new MyNormalStruct();
  myStruct.UpdateValue(2);
  UpdateMyNormalStruct(myStruct);
  Console.WriteLine(myStruct.Value);
 }
}

您可以猜想一下它的運行結果是什么呢? 2 還是 8?

我們來理一下,在 Main 中先調用了結構體自身的方法 UpdateValue 將 Value 修改為 2, 再調用 Program 中的方法 UpdateMyNormalStruct, 而該方法中又調用了 MyNormalStruct 結構體自身的方法 UpdateValue,那么輸出是不是應該是 8 呢? 如果您這么想就錯了。

它的正確輸出結果是 2,這是為什么呢?

這是因為,結構體和許多內置的簡單類型(sbyte、byte、short、ushort、int、uint、long、ulong、char、float、double、decimal、bool 和 enum 類型)一樣,都是值類型,在傳遞參數(shù)的時候以值的方式傳遞。因此調用方法 UpdateMyNormalStruct 時傳遞的是 myStruct 變量的新副本,在此方法中,其實是此副本調用了 UpdateValue 方法,所以原變量 myStruct 的 Value 不會發(fā)生變化。

說到這里,有聰明的朋友可能會想,我們給 UpdateMyNormalStruct 方法的參數(shù)加上 in 修飾符,是不是輸出結果就變?yōu)?8 了,in 參數(shù)不就是引用傳遞嗎?

我們可以試一下,把代碼改成:

static void UpdateMyNormalStruct(in MyNormalStruct myStruct)
{
 myStruct.UpdateValue(8);
}

static void Main(string[] args)
{
 MyNormalStruct myStruct = new MyNormalStruct();
 myStruct.UpdateValue(2);
 UpdateMyNormalStruct(in myStruct);
 Console.WriteLine(myStruct.Value); 
}

運行一下,您會發(fā)現(xiàn),結果依然為 2 !這……就讓人大跌眼鏡了……

用工具查看一下 UpdateMyNormalStruct 方法的中間語言:

.method private hidebysig static 
 void UpdateMyNormalStruct (
 [in] valuetype ConsoleApp4InTest.MyNormalStruct& myStruct
 ) cil managed 
{
 .param [1]
 .custom instance void [System.Runtime]System.Runtime.CompilerServices.IsReadOnlyAttribute::.ctor() = (
 01 00 00 00
 )
 // Method begins at RVA 0x2164
 // Code size 18 (0x12)
 .maxstack 2
 .locals init (
 [0] valuetype ConsoleApp4InTest.MyNormalStruct
 )

 IL_0000: nop
 IL_0001: ldarg.0
 IL_0002: ldobj ConsoleApp4InTest.MyNormalStruct 
 IL_0007: stloc.0
 IL_0008: ldloca.s 0
 IL_000a: ldc.i4.8
 IL_000b: call instance void ConsoleApp4InTest.MyNormalStruct::UpdateValue(int32)
 IL_0010: nop
 IL_0011: ret
} // end of method Program::UpdateMyNormalStruct

您會發(fā)現(xiàn),在 IL_0002、IL_0007 和 IL_0008 這幾行,仍然創(chuàng)建了一個 MyNormalStruct 結構體的防御性副本(defensive copy)。雖然在調用方法 UpdateMyNormalStruct 時以引用的方式傳遞參數(shù),但在方法體中調用結構體自身的 UpdateValue 前,卻創(chuàng)建了一個該結構體的防御性副本,改變的是該副本的 Value。這就有點奇怪了,不是嗎?

Google 了一些資料是這么解釋的:C# 無法知道當它調用一個結構體上的方法(或getter)時,是否也會修改它的值/狀態(tài)。于是,它所做的就是創(chuàng)建所謂的“防御性副本”。當在結構體上運行方法(或getter)時,它會創(chuàng)建傳入的結構體的副本,并在副本上運行方法。這意味著原始副本與傳入時完全相同,調用者傳入的值并沒有被修改。

有沒有辦法讓方法 UpdateMyNormalStruct 調用后輸出 8 呢?您將參數(shù)改成 ref 修飾符試試 :stuck_out_tongue_winking_eye: :grin: :joy:

綜上所述,最好不要把 in 修飾符和一般(非只讀)結構體一起使用,以免產生晦澀難懂的行為,而且可能對性能產生負面影響。

in 參數(shù)的限制

不能將 in、ref 和 out 關鍵字用于以下幾種方法:

  • 異步方法,通過使用 async 修飾符定義。
  • 迭代器方法,包括 yield return 或 yield break 語句。
  • 擴展方法的第一個參數(shù)不能有 in 修飾符,除非該參數(shù)是結構體。
  • 擴展方法的第一個參數(shù),其中該參數(shù)是泛型類型(即使該類型被約束為結構體。)

總結

使用 in 參數(shù),有助于明確表明此參數(shù)不可修改的意圖。

當只讀結構體(readonly struct)的大小大于 IntPtr.Size 3 時,出于性能原因,應將其作為 in 參數(shù)傳遞。

不要將一般(非只讀)結構體作為 in 參數(shù),因為結構體是可變的,反而有可能對性能產生負面影響,并且可能產生晦澀難懂的行為。

到此這篇關于C#中的in參數(shù)與性能分析的文章就介紹到這了,更多相關C#中in參數(shù)與性能內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • 使用淘寶ip地址庫查ip的示例

    使用淘寶ip地址庫查ip的示例

    這篇文章主要介紹了使用淘寶ip地址庫查ip的示例,需要的朋友可以參考下
    2014-03-03
  • .Net常見問題之C#中的委托

    .Net常見問題之C#中的委托

    這篇文章主要介紹了.Net常見問題委托概念的介紹,委托類似于C++中的函數(shù)指針,但是又有所不同,下面就針對委托這個概念給大家詳細介紹下,需要的朋友可以參考下
    2015-07-07
  • C#實現(xiàn)拼手氣紅包算法

    C#實現(xiàn)拼手氣紅包算法

    這篇文章主要為大家詳細介紹了C#實現(xiàn)拼手氣紅包算法,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-09-09
  • C#開啟線程的四種方式示例詳解

    C#開啟線程的四種方式示例詳解

    今天小編就為大家分享一篇關于C#開啟線程的四種方式示例詳解,小編覺得內容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2018-10-10
  • c#對xml增刪改查操作示例

    c#對xml增刪改查操作示例

    本文主要介紹c#對xml增刪改查操作的示例,大家參考使用吧
    2014-01-01
  • C#在MEF框架中手動導入依賴模塊

    C#在MEF框架中手動導入依賴模塊

    這篇文章介紹了C#在MEF框架中手動導入依賴模塊的方法,文中通過示例代碼介紹的非常詳細。對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-06-06
  • C#查找字符串所有排列組合的方法

    C#查找字符串所有排列組合的方法

    這篇文章主要介紹了C#查找字符串所有排列組合的方法,涉及C#字符串操作的相關技巧,非常具有實用價值,需要的朋友可以參考下
    2015-04-04
  • WPF實現(xiàn)調用本機攝像頭的示例代碼

    WPF實現(xiàn)調用本機攝像頭的示例代碼

    這篇文章主要介紹了如何利用WPF實現(xiàn)調用本機攝像頭,文中的示例代碼講解詳細,對我們學習或工作有一定幫助,需要的可以參考一下
    2022-08-08
  • C#第三方日志插件Log4net的配置文件詳解

    C#第三方日志插件Log4net的配置文件詳解

    Log4net是一個穩(wěn)定且功能豐富的日志庫,已經存在多年并且被廣泛使用,這篇文章主要為大家詳細介紹了Log4net日志插件的配置文件信息,感興趣的可以了解下
    2024-03-03
  • Unity實現(xiàn)見縫插針小游戲

    Unity實現(xiàn)見縫插針小游戲

    這篇文章主要為大家詳細介紹了Unity實現(xiàn)見縫插針小游戲,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-04-04

最新評論

石楼县| 托里县| 通海县| 大庆市| 黑河市| 沧州市| 运城市| 鞍山市| 焦作市| 河南省| 泸水县| 济源市| 和顺县| 含山县| 丰顺县| 饶河县| 龙川县| 彭山县| 手机| 深州市| 蕲春县| 德令哈市| 木兰县| 沽源县| 遂昌县| 太仓市| 新民市| 馆陶县| 新竹县| 庄浪县| 梅河口市| 洪洞县| 尼玛县| 宜君县| 象山县| 高碑店市| 玉龙| 共和县| 兴安县| 汶上县| 泾阳县|