日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > C# >内容正文

C#

C# 中的 in 参数和性能分析

發(fā)布時(shí)間:2023/12/4 C# 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C# 中的 in 参数和性能分析 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

in?修飾符也是從 C# 7.2 開始引入的,它與我們上一篇中討論的 《C# 中的只讀結(jié)構(gòu)體(readonly struct)》[1]?是緊密相關(guān)的。

in 修飾符

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

  • ref?修飾符,指定參數(shù)由引用傳遞,可以由調(diào)用方法讀取或?qū)懭搿?/p>

  • out?修飾符,指定參數(shù)由引用傳遞,必須由調(diào)用方法寫入。

  • in?修飾符,指定參數(shù)由引用傳遞,可以由調(diào)用方法讀取,但不可以寫入。

舉個(gè)簡(jiǎn)單的例子:

struct Product {public int ProductId { get; set; }public string ProductName { get; set; } }public static void Modify(in Product product) {//product = new Product(); // 錯(cuò)誤 CS8331 無法分配到 變量 'in Product',因?yàn)樗侵蛔x變量//product.ProductName = "測(cè)試商品"; // 錯(cuò)誤 CS8332 不能分配到 變量 'in Product' 的成員,因?yàn)樗侵蛔x變量Console.WriteLine($"Id: {product.ProductId}, Name: {product.ProductName}"); // OK }

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

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

但是對(duì)于有些很大(比如有很多字段或?qū)傩?#xff09;的結(jié)構(gòu)體,將其作為方法參數(shù),在緊湊的循環(huán)或關(guān)鍵代碼路徑中調(diào)用方法時(shí),復(fù)制這些結(jié)構(gòu)的成本就會(huì)很高。當(dāng)所調(diào)用的方法不修改該參數(shù)的狀態(tài),使用新的修飾符?in?聲明參數(shù)以指定此參數(shù)可以按引用安全傳遞,可以避免(可能產(chǎn)生的)高昂的復(fù)制成本,從而提高代碼運(yùn)行的性能。

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

為了測(cè)試?in?修飾符對(duì)性能的提升,我定義了兩個(gè)較大的結(jié)構(gòu)體,一個(gè)是可變的結(jié)構(gòu)體?NormalStruct,一個(gè)是只讀的結(jié)構(gòu)體?ReadOnlyStruct,都定義了 30 個(gè)屬性,然后定義三個(gè)測(cè)試方法:

  • DoNormalLoop?方法,參數(shù)不加修飾符,傳入一般結(jié)構(gòu)體,這是以前比較常見的做法。

  • DoNormalLoopByIn?方法,參數(shù)加?in?修飾符,傳入一般結(jié)構(gòu)體。

  • DoReadOnlyLoopByIn?方法,參數(shù)加?in?修飾符,傳入只讀結(jié)構(gòu)體。

代碼如下所示:

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è)務(wù)邏輯return 0M;}public decimal ComputeIn(in NormalStruct s){//業(yè)務(wù)邏輯return 0M;}public decimal ComputeIn(in ReadOnlyStruct s){//業(yè)務(wù)邏輯return 0M;} }

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

使用 BenchmarkDotNet 工具測(cè)試三個(gè)方法的運(yùn)行時(shí)間,結(jié)果如下:

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

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

在 Parallel.For 中使用

在上面簡(jiǎn)單的?for?循環(huán)中,我們看到?in?參數(shù)有助于性能的提升,那么在并行運(yùn)算中呢?我們把上面的?for?循環(huán)改成使用?Parallel.For?來實(shí)現(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; }

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

使用 BenchmarkDotNet 工具測(cè)試三個(gè)方法的運(yùn)行時(shí)間,結(jié)果如下:

| 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ù)會(huì)得到更好的性能。

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

我們來看一個(gè)例子,定義一個(gè)一般的結(jié)構(gòu)體,包含一個(gè)屬性?Value?和 一個(gè)修改該屬性的方法?UpdateValue。然后在別的地方也定義一個(gè)方法?UpdateMyNormalStruct?來修改該結(jié)構(gòu)體的屬性?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);} }

您可以猜想一下它的運(yùn)行結(jié)果是什么呢?2 還是 8?

我們來理一下,在?Main?中先調(diào)用了結(jié)構(gòu)體自身的方法?UpdateValue?將?Value?修改為 2, 再調(diào)用?Program?中的方法?UpdateMyNormalStruct, 而該方法中又調(diào)用了?MyNormalStruct?結(jié)構(gòu)體自身的方法?UpdateValue,那么輸出是不是應(yīng)該是 8 呢?如果您這么想就錯(cuò)了。
它的正確輸出結(jié)果是?2,這是為什么呢?

這是因?yàn)?#xff0c;結(jié)構(gòu)體和許多內(nèi)置的簡(jiǎn)單類型(sbyte、byte、short、ushort、int、uint、long、ulong、char、float、double、decimal、bool 和 enum 類型)一樣,都是值類型,在傳遞參數(shù)的時(shí)候以值的方式傳遞。因此調(diào)用方法?UpdateMyNormalStruct?時(shí)傳遞的是?myStruct?變量的新副本,在此方法中,其實(shí)是此副本調(diào)用了?UpdateValue?方法,所以原變量?myStruct?的?Value?不會(huì)發(fā)生變化。

說到這里,有聰明的朋友可能會(huì)想,我們給?UpdateMyNormalStruct?方法的參數(shù)加上?in?修飾符,是不是輸出結(jié)果就變?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); }

運(yùn)行一下,您會(huì)發(fā)現(xiàn),結(jié)果依然為?2?!這……就讓人大跌眼鏡了……
用工具查看一下?UpdateMyNormalStruct?方法的中間語(yǔ)言:

.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: nopIL_0001: ldarg.0IL_0002: ldobj ConsoleApp4InTest.MyNormalStruct IL_0007: stloc.0IL_0008: ldloca.s 0IL_000a: ldc.i4.8IL_000b: call instance void ConsoleApp4InTest.MyNormalStruct::UpdateValue(int32)IL_0010: nopIL_0011: ret } // end of method Program::UpdateMyNormalStruct

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

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

有沒有辦法讓方法?UpdateMyNormalStruct?調(diào)用后輸出 8 呢?您將參數(shù)改成?ref?修飾符試試看 ???? ???? ????

綜上所述,最好不要把?in?修飾符和一般(非只讀)結(jié)構(gòu)體一起使用,以免產(chǎn)生晦澀難懂的行為,而且可能對(duì)性能產(chǎn)生負(fù)面影響。

in 參數(shù)的限制

不能將?in、ref?和?out?關(guān)鍵字用于以下幾種方法:

  • 異步方法,通過使用?async?修飾符定義。

  • 迭代器方法,包括?yield return?或?yield break?語(yǔ)句。

  • 擴(kuò)展方法的第一個(gè)參數(shù)不能有?in?修飾符,除非該參數(shù)是結(jié)構(gòu)體。

  • 擴(kuò)展方法的第一個(gè)參數(shù),其中該參數(shù)是泛型類型(即使該類型被約束為結(jié)構(gòu)體。)

總結(jié)

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

  • 當(dāng)只讀結(jié)構(gòu)體(readonly struct)的大小大于?IntPtr.Size?[3]?時(shí),出于性能原因,應(yīng)將其作為?in?參數(shù)傳遞。

  • 不要將一般(非只讀)結(jié)構(gòu)體作為?in?參數(shù),因?yàn)榻Y(jié)構(gòu)體是可變的,反而有可能對(duì)性能產(chǎn)生負(fù)面影響,并且可能產(chǎn)生晦澀難懂的行為。


相關(guān)鏈接:

  • https://mp.weixin.qq.com/s/wwVZbdY7m7da1nmIKb2jCA?C# 中的只讀結(jié)構(gòu)體???

  • https://mp.weixin.qq.com/s/wVikRMfc4BbrB6WbDy1gXw?C# 中 Struct 和 Class 的區(qū)別總結(jié)???

  • https://docs.microsoft.com/zh-cn/dotnet/api/system.intptr.size#System_IntPtr_Size?IntPtr.Size???

  • 作者 :技術(shù)譯民??

    出品 :技術(shù)譯站(https://ITTranslator.cn/)

    總結(jié)

    以上是生活随笔為你收集整理的C# 中的 in 参数和性能分析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。