網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
在?C#?中使用?Span<T>?和?Memory<T>?編寫(xiě)高性能代碼的詳細(xì)步驟_C#教程
作者:癡者工良 ? 更新時(shí)間: 2022-10-17 編程語(yǔ)言本文采用半譯方式。
在本文中,將會(huì)介紹 C# 7.2 中引入的新類型:Span 和 Memory,文章深入研究?Span<T>
?和?Memory<T>
?,并演示如何在 C# 中使用它們。
本文所有代碼用例在 .NET 6.0 下運(yùn)行。
.NET 中支持的內(nèi)存類型
.NET 中,開(kāi)發(fā)者能夠使用的三種內(nèi)存類型,分別是:
-
Stack memory 堆棧內(nèi)存:?駐留在堆棧中,并使用
stackalloc
?關(guān)鍵詞分配; - Managed memory 托管內(nèi)存:?駐留在堆中并由 GC 管理;
-
Unmanaged memory 非托管內(nèi)存:?駐留在非托管堆中,并通過(guò)調(diào)用?
Marshal.AllocHGlobal
?or 或者Marshal.AllocCoTaskMem
?方法 分配;
.NET Core 2.1 中新增的類型
.NET Core 2.1 中新引入的類型包括:
System.Span:?這以類型安全和內(nèi)存安全的方式表示任意內(nèi)存的連續(xù)部分;
System.ReadOnlySpan:?這表示任意連續(xù)內(nèi)存區(qū)域的類型安全和內(nèi)存安全只讀表示形式;
**System.Memory: ** 這表示一個(gè)連續(xù)的內(nèi)存區(qū)域;
-
System.ReadOnlyMemory:?類似
ReadOnlySpan
, 此類型表示內(nèi)存的連續(xù)部分ReadOnlySpan
, 它不是 ByRef 類型;譯者注:
ByRef?
類型指的是?ref readonly struct
。
訪問(wèn)連續(xù)內(nèi)存: Span 和 Memory
開(kāi)發(fā)者可能經(jīng)常需要在應(yīng)用程序中處理大量數(shù)據(jù),例如字符串處理在任何應(yīng)用程序中都是至關(guān)重要的,因此開(kāi)發(fā)者必須遵循推薦的實(shí)踐以避免不必要的分配。開(kāi)發(fā)者可以使用不安全的代碼塊和指針直接操作內(nèi)存,但是這種方法有相當(dāng)大的風(fēng)險(xiǎn),指針操作容易出現(xiàn)錯(cuò)誤,如溢出、空指針訪問(wèn)、緩沖區(qū)溢出和懸空指針。如果 bug 只影響堆?;蜢o態(tài)內(nèi)存區(qū)域,那么它將是無(wú)害的,但是如果它影響關(guān)鍵的系統(tǒng)內(nèi)存區(qū)域,則可能導(dǎo)致應(yīng)用程序崩潰。
因此,出現(xiàn)了?Span<T>
?和?Memory<T>
?,能夠以安全的方式使用指針訪問(wèn)內(nèi)存。
Span<T>
?和?Memory<T>
?是 .NET 新引入的類型(都是?struct
),它們提供了一種類型安全的方法來(lái)訪問(wèn)任意內(nèi)存的連續(xù)區(qū)域。Span<T>
?和?Memory<T>
?都是 System 命名空間的一部分,表示連續(xù)的內(nèi)存塊,沒(méi)有任何復(fù)制語(yǔ)義。
C# 新版本添加了?Span<T>
?、?Memory<T>
?、?ReadOnlySpan
?和?ReadOnlyMemory
?類型 ,它們可以幫助開(kāi)發(fā)者在安全和性能方面直接使用內(nèi)存。
這些新類型在?System.Memory
?命名空間中,適用于需要處理大量數(shù)據(jù)或希望避免不必要的內(nèi)存分配(例如在使用緩沖區(qū)時(shí))的高性能場(chǎng)景。與在 GC 堆上分配內(nèi)存的數(shù)組類型不同,這些新類型提供了對(duì)任意托管或本機(jī)內(nèi)存的連續(xù)區(qū)域的抽象,而不需要在 GC 堆上分配內(nèi)存。
譯者注:因?yàn)樗鼈兌际?struct,會(huì)被分配到棧中。
Span<T>
?和?Memory<T>
?結(jié)構(gòu)體為數(shù)組、字符串或任何連續(xù)的托管或非托管內(nèi)存塊提供低級(jí)接口,它們的主要功能是促進(jìn)微優(yōu)化和編寫(xiě)低分配代碼,以減少托管內(nèi)存分配,從而減少垃圾收集器的負(fù)擔(dān)。它們還允許切片或處理數(shù)組、字符串或內(nèi)存塊的某個(gè)部分,而無(wú)需復(fù)制原始內(nèi)存塊。Span<T>
?和?Memory<T>
?在高性能領(lǐng)域非常有用,例如 ASP.NET Core 6 request-processing 管道。
Span 介紹
Span<T>
?(早期稱為 Slice) 出現(xiàn)于 C# 7.2/NET Core 2.1,創(chuàng)建它的開(kāi)銷幾乎為零,它提供了一種使用連續(xù)內(nèi)存塊的類型安全方法,例如:
- Arrays and subarrays 數(shù)組和子數(shù)組
- Strings and substrings 字符串和子字符串
- Unmanaged memory buffers 非托管內(nèi)存緩沖區(qū)
Span 類型表示駐留在托管堆、堆棧甚至非托管內(nèi)存中的連續(xù)內(nèi)存塊,如果創(chuàng)建一個(gè)基元類型的數(shù)組(使用?stackalloc
?創(chuàng)建),它將在堆棧上分配,并且不需要垃圾回收來(lái)管理其生存期。Span<T>?
能夠指向分配給堆棧或堆上的內(nèi)存塊。但是,因?yàn)?Span<T>
?被定義為 ref 結(jié)構(gòu),所以它應(yīng)該只駐留在堆棧上。
以下是一目了然的?Span<T>
?的特征:
- Value type 值類型
- Low or zero overhead 低或零開(kāi)銷
- High performance 高性能
- Provides memory and type safety 提供內(nèi)存和類型安全
開(kāi)發(fā)者可以將 Span 與下列任一項(xiàng)一起使用
- Arrays
- Strings
- Native buffers 本地緩沖區(qū)
可以轉(zhuǎn)換為?Span<T>
?的類型列表如下:
- Arrays
- Pointers 指針
- IntPtr 指針
- stackalloc
開(kāi)發(fā)者可以將以下所有內(nèi)容轉(zhuǎn)換為?ReadOnlySpan<T>
?:
- Arrays
- Pointers 指針
- IntPtr 指針
- stackalloc
- string
Span<T>
?是一個(gè)僅堆棧類型, 準(zhǔn)確地說(shuō)它是一個(gè) ByRef 類型。因此,既不能將 span 裝箱,也不能顯示為僅限堆棧類型的字段,也不能在泛型參數(shù)中使用它們。但是,可以使用 span 來(lái)表示返回值或方法參數(shù)。請(qǐng)參考下面給出的代碼片段,它說(shuō)明了 Span 結(jié)構(gòu)的完整源代碼:
public readonly ref struct Span<T> { internal readonly ByReference<T> _pointer; private readonly int _length; //Other members }
因?yàn)?Span 定義時(shí),是?public readonly ref struct Span<T>
,表示只能在堆棧中分配。
開(kāi)發(fā)者可以在這里查看?struct Span<T>
?的完整源代碼:?https://github.com/dotnet/corefx/blob/master/src/common/src/corelib/system/Span.cs。
Span<T>
?源代碼顯示它基本上包含兩個(gè)只讀字段: 一個(gè)本機(jī)指針和一個(gè)長(zhǎng)度屬性,表示 Span 包含的元素?cái)?shù)。
Span 的使用方式與數(shù)組相同,但是與數(shù)組不同,它可以引用堆棧內(nèi)存,即堆棧上分配的內(nèi)存、托管內(nèi)存和本機(jī)內(nèi)存。這為開(kāi)發(fā)者提供了一種簡(jiǎn)單的方法來(lái)利用以前只有在處理非托管代碼時(shí)才能獲得的性能改進(jìn)。
若要?jiǎng)?chuàng)建空的 Span,可以使用 Span.Empty 屬性:
Span<char> span = Span<char>.Empty;
下面的代碼片段演示如何在托管內(nèi)存中創(chuàng)建 Byte 數(shù)組,然后從中創(chuàng)建 span 實(shí)例。
var array = new byte[100]; var span = new Span<byte>(array);
C# 中的 Span
下面是如何在堆棧中分配一塊內(nèi)存并使用 Span 指向它:
Span<byte> span = stackalloc byte[100];
下面的代碼片段顯示了如何使用字節(jié)數(shù)組創(chuàng)建 Span、如何將整數(shù)存儲(chǔ)在字節(jié)數(shù)組中以及如何計(jì)算存儲(chǔ)的所有整數(shù)的總和。
var array = new byte[100]; var span = new Span<byte>(array); byte data = 0; for (int index = 0; index < span.Length; index++) span[index] = data++; int sum = 0; foreach (int value in array) sum += value;
下面的代碼片段從本機(jī)內(nèi)存(非托管內(nèi)存)創(chuàng)建一個(gè) Span:
var nativeMemory = Marshal.AllocHGlobal(100); Span<byte> span; unsafe { span = new Span<byte>(nativeMemory.ToPointer(), 100); }
現(xiàn)在可以使用下面的代碼片段在 Span 指向的內(nèi)存中存儲(chǔ)整數(shù),并顯示存儲(chǔ)的所有整數(shù)的總和:
byte data = 0; for (int index = 0; index < span.Length; index++) span[index] = data++; int sum = 0; foreach (int value in span) sum += value; Console.WriteLine ($"The sum of the numbers in the array is {sum}"); Marshal.FreeHGlobal(nativeMemory);
還可以使用 stackalloc 關(guān)鍵字在堆棧內(nèi)存中分配 Span,如下所示:
byte data = 0; Span<byte> span = stackalloc byte[100]; for (int index = 0; index < span.Length; index++) span[index] = data++; int sum = 0; foreach (int value in span) sum += value; Console.WriteLine ($"The sum of the numbers in the array is {sum}");
需要開(kāi)啟不安全代碼設(shè)置。
Span 和 Arrays
切片允許將數(shù)據(jù)視為邏輯塊,然后可以以最小的資源開(kāi)銷處理這些邏輯塊。Span<T>
?可以包裝整個(gè)數(shù)組,因?yàn)樗С智衅?,所以可以讓它指向?shù)組中的任何連續(xù)區(qū)域。下面的代碼片段顯示了如何使用?Span<T>
?指向數(shù)組中由三個(gè)元素組成的片段。
int[] array = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 } ; Span<int> slice = new Span<int>(array, 2, 3);
作為?Span<T>
?struct 的一部分,Slice 方法有兩個(gè)重載,允許基于索引創(chuàng)建,這允許將Span<T>
?數(shù)據(jù)作為一系列邏輯塊來(lái)處理,這些邏輯塊可以單獨(dú)處理,也可以按照數(shù)據(jù)處理流水線的各個(gè)部分的要求來(lái)處理。
開(kāi)發(fā)者可以使用?Span<T>
?來(lái)包裝整個(gè)數(shù)組。因?yàn)樗С智衅?,所以它不僅可以指向數(shù)組的第一個(gè)元素,還可以指向數(shù)組中任何連續(xù)的元素范圍。
foreach (int i in slice) Console.WriteLine($"{i} ");
執(zhí)行前面的代碼片段時(shí),分片數(shù)組中的整數(shù)將顯示在控制臺(tái)上,如圖2所示。
Span 和 ReadOnlySpan
ReadOnlySpan<T>
?實(shí)例通常用于引用數(shù)組項(xiàng)或數(shù)組的塊。與數(shù)組不同,ReadOnlySpan<T>
?實(shí)例可以引用本機(jī)內(nèi)存、托管內(nèi)存或堆棧內(nèi)存。Span<T>
?和?ReadOnlySpan<T>
?都提供了連續(xù)內(nèi)存區(qū)域的類型安全表示,?Span<T>
?提供對(duì)內(nèi)存區(qū)域的讀寫(xiě)訪問(wèn),?ReadOnlySpan<T>
?提供對(duì)內(nèi)存段的只讀訪問(wèn)。
下面的代碼片段說(shuō)明了如何使用 ReadOnlySpan 在 C# 中切割字符串的一部分:
ReadOnlySpan<char> readOnlySpan = "This is a sample data for testing purposes."; int index = readOnlySpan.IndexOf(' '); var data = ((index < 0) ? readOnlySpan : readOnlySpan.Slice(0, index)).ToArray();
Memory 入門(mén)
Memory<T>
?是一個(gè)引用類型,它表示內(nèi)存的一個(gè)連續(xù)區(qū)域,具有一個(gè)長(zhǎng)度,但不一定從索引0開(kāi)始,可以是另一個(gè) Memory 中的許多區(qū)域之一。由 Memory 表示的內(nèi)存甚至可能不是開(kāi)發(fā)者自己的進(jìn)程,因?yàn)樗赡芤呀?jīng)在非托管代碼中分配。內(nèi)存對(duì)于表示非連續(xù)緩沖區(qū)中的數(shù)據(jù)非常有用,因?yàn)樗试S開(kāi)發(fā)者像對(duì)待單個(gè)連續(xù)緩沖區(qū)一樣對(duì)待它們,而不需要進(jìn)行復(fù)制。
以下是 Memory?的定義:
public struct Memory<T> { void* _ptr; T[] _array; int _offset; int _length; public Span<T> Span => _ptr == null ? new Span<T>(_array, _offset, _length) : new Span<T>(_ptr, _length); }
除了包含?Span<T>
?功能外,Memory<T>
?還提供了一個(gè)安全的、可切片的視圖,可以進(jìn)入任何連續(xù)的緩沖區(qū),無(wú)論是數(shù)組還是字符串。與?Span<T>
?不同,它沒(méi)有僅限于堆棧的約束,因?yàn)樗皇穷愃朴?ref 的類型。因此,開(kāi)發(fā)者可以將它放在堆上,在集合中或異步等待中使用它,將它保存為字段或裝箱,就像對(duì)待任何其他 C# 結(jié)構(gòu)一樣。
當(dāng)需要修改或處理?Memory<T>
?引用的緩沖區(qū)時(shí),Span<T>?
屬性允許開(kāi)發(fā)者獲得高效的索引功能。相反,Memory<T>
?是一種比 Span?更通用和高級(jí)的交換類型,它具有一個(gè)名為?ReadOnlyMemory<T>
?的不可變的只讀對(duì)應(yīng)物。
盡管?Span<T>
?和?Memory<T>
?都代表一個(gè)連續(xù)的內(nèi)存塊,但與?Span<T>?
不同,Memory<T>
?不是一個(gè) ref 結(jié)構(gòu)。因此,與?Span<T>
?相反,開(kāi)發(fā)者可以在托管堆上的任何位置使用?Memory<T>?
。因此,在?Memory<T>
?中沒(méi)有與?Span<T>
?中相同的限制,開(kāi)發(fā)者可以使用?Memory<T>
?作為類字段,并且可以跨 await 和 yield 邊界(下面會(huì)說(shuō)到)。
ReadOnlyMemory
與?ReadOnlySpan<T>
?類似,ReadOnlyMemory<T>
?表示對(duì)連續(xù)內(nèi)存區(qū)域的只讀訪問(wèn),但與?ReadOnlySpan<T>
?不同,它不是 ByRef 類型。
現(xiàn)在請(qǐng)參考下面的字符串,其中包含由空格字符分隔的國(guó)家名稱。
string countries = "India Belgium Australia USA UK Netherlands"; var countries = ExtractStrings("India Belgium Australia USA UK Netherlands".AsMemory());
通過(guò)提取字符串的方法提取每個(gè)國(guó)家的名稱,如下所示:
public static IEnumerable<ReadOnlyMemory <char>> ExtractStrings(ReadOnlyMemory<char> c) { int index = 0, length = c.Length; for (int i = 0; i < length; i++) { if (char.IsWhiteSpace(c.Span[i]) || i == length) { yield return c[index..i]; index = i + 1; } } }
開(kāi)發(fā)者可以調(diào)用上述方法,并使用以下代碼片段在控制臺(tái)窗口中顯示國(guó)家名稱:
var data = ExtractStrings(countries.AsMemory()); foreach(var str in data) Console.WriteLine(str);
Span 和 Memory 的優(yōu)勢(shì)
使用 Span 和 Memory 類型的主要優(yōu)點(diǎn)是提高了性能。開(kāi)發(fā)者可以通過(guò)使用 stackalloc 關(guān)鍵字來(lái)分配堆棧上的內(nèi)存,該關(guān)鍵字分配一個(gè)未初始化的塊,該塊是?T[size]
類型的實(shí)例。如果開(kāi)發(fā)者的數(shù)據(jù)已經(jīng)在堆棧上,則不需要這樣做,但是對(duì)于大型對(duì)象,這樣做很有用,因?yàn)橐赃@種方式分配的數(shù)組只有在其作用域持續(xù)存在時(shí)才存在。如果使用堆分配的數(shù)組,可以通過(guò)?Slice()
這樣的方法傳遞它們,并在不復(fù)制任何數(shù)據(jù)的情況下創(chuàng)建視圖。
這里還有一些好處:
- 它們減少了垃圾收集器的分配數(shù)量。它們還減少了數(shù)據(jù)的副本數(shù)量,并提供了一種更有效的方法來(lái)同時(shí)處理多個(gè)緩沖區(qū);
- 它們?cè)试S開(kāi)發(fā)者編寫(xiě)高性能代碼。例如,如果開(kāi)發(fā)者有一大塊內(nèi)存需要分成小塊,那么使用 Span 作為原始內(nèi)存的視圖。這允許開(kāi)發(fā)者的應(yīng)用程序直接從原始緩沖區(qū)訪問(wèn)字節(jié),而無(wú)需復(fù)制;
- 它們?cè)试S開(kāi)發(fā)者直接訪問(wèn)內(nèi)存而無(wú)需復(fù)制內(nèi)存。這在使用本機(jī)庫(kù)或與其他語(yǔ)言進(jìn)行互操作時(shí)特別有用;
- 它們?cè)试S開(kāi)發(fā)者在性能至關(guān)重要的緊密循環(huán)(如加密或網(wǎng)絡(luò)包檢查)中消除邊界檢查;
- 它們?cè)试S開(kāi)發(fā)者消除與通用集合(如 List)相關(guān)的裝箱和取消裝箱成本;
- 通過(guò)使用單一數(shù)據(jù)類型(Span)而不是兩種不同類型(Array 和 ArraySegment) ,它們可以編寫(xiě)更容易理解的代碼;
連續(xù)和非連續(xù)內(nèi)存緩沖區(qū)
連續(xù)內(nèi)存緩沖區(qū)是將數(shù)據(jù)保存在順序相鄰位置的內(nèi)存塊,換句話說(shuō),所有的字節(jié)在內(nèi)存中都是相鄰的。數(shù)組表示連續(xù)的內(nèi)存緩沖區(qū)。
例如:
int[] values = new int[5];
上面示例中的五個(gè)整數(shù)將從第一個(gè)元素(值[0])開(kāi)始,按順序放置在內(nèi)存中的五個(gè)位置。
與連續(xù)緩沖區(qū)不同,開(kāi)發(fā)者可以使用非連續(xù)緩沖區(qū)來(lái)處理多個(gè)數(shù)據(jù)塊并不相鄰的情況,或者在使用非托管代碼時(shí)使用非連續(xù)緩沖區(qū),Span 和 Memory 類型是專門(mén)為非連續(xù)緩沖區(qū)設(shè)計(jì)的,并提供了使用它們的方便方法。
非連續(xù)的內(nèi)存區(qū)域不能保證元素以任何特定的順序存儲(chǔ),也不能保證元素在內(nèi)存中緊密地存儲(chǔ)在一起。非連續(xù)緩沖區(qū)(如 ReadOnlySequence (與段一起使用時(shí)))駐留在內(nèi)存的單獨(dú)區(qū)域中,這些區(qū)域可能分散在堆中,不能被單個(gè)指針訪問(wèn)。
例如,IEnumable 是非連續(xù)的,因?yàn)樵陂_(kāi)發(fā)者逐個(gè)枚舉每個(gè)項(xiàng)之前,無(wú)法知道下一個(gè)項(xiàng)將在哪里。為了表示段之間的這些間隔,必須使用附加數(shù)據(jù)來(lái)跟蹤每個(gè)段的開(kāi)始和結(jié)束位置。
不連續(xù)的緩沖區(qū): ReadOnly 序列
讓作者們假設(shè)開(kāi)發(fā)者正在使用一個(gè)不連續(xù)的緩沖區(qū)。例如,數(shù)據(jù)可能來(lái)自網(wǎng)絡(luò)流、數(shù)據(jù)庫(kù)調(diào)用或文件流。這些場(chǎng)景中的每一個(gè)都可以有多個(gè)大小不同的緩沖區(qū)。一個(gè) ReadOnlySequence 實(shí)例可以包含一個(gè)或多個(gè)內(nèi)存段,每個(gè)段可以有自己的 Memory 實(shí)例。因此,單個(gè) ReadOnlySequence 實(shí)例可以更好地管理可用內(nèi)存,并提供比許多串聯(lián)內(nèi)存實(shí)例更好的性能。
開(kāi)發(fā)者可以使用 SequenceReader 類上的工廠方法?Create()
以及?AsReadOnlySequence()
等其他方法創(chuàng)建 ReadOnlySequence 實(shí)例。Create()
?方法有幾個(gè)重載,允許開(kāi)發(fā)者傳入?byte []
?或?ArraySegment
、字節(jié)數(shù)組序列(IEnumable)或?IReadOnlyCollection
?/IReadOnlyList/IList
?/?ICollection
?字節(jié)數(shù)組集合(byte []
)和?ArraySegment
。
開(kāi)發(fā)者現(xiàn)在知道?Span<T>
?和?Memory<T>
?提供了對(duì)連續(xù)內(nèi)存緩沖區(qū)(如數(shù)組)的支持。系統(tǒng)。緩沖區(qū)命名空間包含一個(gè)名為?ReadOnlySequense<T>
?的結(jié)構(gòu),該結(jié)構(gòu)支持處理不連續(xù)的內(nèi)存緩沖區(qū)。下面的代碼片段說(shuō)明了如何在 C# 中使用?ReadOnlySequence<T>?
:
int[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; var readOnlySequence = new ReadOnlySequence<int>(array); var slicedReadOnlySequence = readOnlySequence.Slice(1, 5);
開(kāi)發(fā)者也可以使用?ReadOnlyMemory<T>
?,如下所示:
int[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; ReadOnlyMemory<int> memory = array; var readOnlySequence = new ReadOnlySequence<int>(memory); var slicedReadOnlySequence = readOnlySequence.Slice(1, 5);
實(shí)際場(chǎng)景
現(xiàn)在讓作者們來(lái)談?wù)勔粋€(gè)現(xiàn)實(shí)中的場(chǎng)景,以及?Span<T>
?和?Memory<T>
?是如何起作用的。請(qǐng)考慮以下字符串?dāng)?shù)組,其中包含從日志文件檢索到的日志數(shù)據(jù):
string[] logs = new string[] { "a1K3vlCTZE6GAtNYNAi5Vg::05/12/2022 09:10:00 AM::http://localhost:2923/api/customers/getallcustomers", "mpO58LssO0uf8Ced1WtAvA::05/12/2022 09:15:00 AM::http://localhost:2923/api/products/getallproducts", "2KW1SfJOMkShcdeO54t1TA::05/12/2022 10:25:00 AM::http://localhost:2923/api/orders/getallorders", "x5LmCTwMH0isd1wiA8gxIw::05/12/2022 11:05:00 AM::http://localhost:2923/api/orders/getallorders", "7IftPSBfCESNh4LD9yI6aw::05/12/2022 11:40:00 AM::http://localhost:2923/api/products/getallproducts" };
請(qǐng)記住,開(kāi)發(fā)者可以擁有數(shù)百萬(wàn)條日志記錄,因此性能至關(guān)重要。這個(gè)示例只是從大量日志數(shù)據(jù)中提取的日志數(shù)據(jù)。每個(gè)行的數(shù)據(jù)由 HTTP 請(qǐng)求 ID、 HTTP 請(qǐng)求的 DateTime 和端點(diǎn) URL 組成?,F(xiàn)在假設(shè)開(kāi)發(fā)者需要從這些數(shù)據(jù)中提取請(qǐng)求 ID 和端點(diǎn) URL。
開(kāi)發(fā)者需要一個(gè)高性能的解決方案。如果使用 String 類的 Substring 方法,就會(huì)創(chuàng)建許多字符串對(duì)象,這也會(huì)降低應(yīng)用程序的性能。最好的解決方案是在這里使用?Span<T>
?來(lái)避免分配。解決這個(gè)問(wèn)題的方法是使用?Span<T>
?和 Slice 方法,如下一節(jié)所示。
Benchmarking 基準(zhǔn)測(cè)試
是時(shí)候測(cè)量一下了。現(xiàn)在讓作者們對(duì)?Span<T>
?struct 與 String 類的 Substring 方法的性能進(jìn)行基準(zhǔn)測(cè)試。
安裝 NuGet 包
目前為止還不錯(cuò)。下一步是安裝必要的 NuGet 包。要將所需的包安裝到項(xiàng)目中,右鍵單擊解決方案并選擇?Manage NuGet Packages for Solution...?
?,F(xiàn)在在搜索框中搜索名為 BenchmarkDotNet 的軟件包并安裝它?;蛘?,開(kāi)發(fā)者也可以在NuGet Package Manager?
命令提示符下鍵入以下命令:
PM> Install-Package BenchmarkDotNet
Benchmarking Span
現(xiàn)在讓作者們研究一下如何對(duì) Substring 和 Slice 方法的性能進(jìn)行基準(zhǔn)測(cè)試。使用清單1中的代碼創(chuàng)建一個(gè)名為 BenchmarkPerformance 的新類。開(kāi)發(fā)者應(yīng)該注意在 GlobalSetup 方法中如何設(shè)置數(shù)據(jù)以及 GlobalSetup 屬性的用法。
[MemoryDiagnoser] [Orderer(BenchmarkDotNet.Order.SummaryOrderPolicy.FastestToSlowest)] [RankColumn] public class BenchmarkPerformance { [Params(100, 200)] public int N; string countries = null; int index, numberOfCharactersToExtract; [GlobalSetup] public void GlobalSetup() { countries = "India, USA, UK, Australia, Netherlands, Belgium"; index = countries.LastIndexOf(",",StringComparison.Ordinal); numberOfCharactersToExtract = countries.Length - index; } }
現(xiàn)在,編寫(xiě)名為 Substring 和 Span 的兩個(gè)方法,如清單2所示。前者使用 String 類的 Substring 方法檢索最后一個(gè)國(guó)家名稱,而后者使用 Slice 方法提取最后一個(gè)國(guó)家名稱。
[Benchmark] public void Substring() { for(int i = 0; i < N; i++) { var data = countries.Substring(index + 1, numberOfCharactersToExtract - 1); } } [Benchmark(Baseline = true)] public void Span() { for(int i=0; i < N; i++) { var data = countries.AsSpan().Slice(index + 1, numberOfCharactersToExtract - 1); } }
執(zhí)行基準(zhǔn)測(cè)試
class Program { static void Main() { BenchmarkRunner.Run<BenchmarkPerformance>(); } }
若要執(zhí)行基準(zhǔn)測(cè)試,請(qǐng)將項(xiàng)目的編譯模式設(shè)置為“發(fā)布”,并在項(xiàng)目文件所在的同一文件夾中運(yùn)行以下命令:
dotnet run -c Release
下圖顯示了基準(zhǔn)測(cè)試的執(zhí)行結(jié)果。
解讀基準(zhǔn)測(cè)試結(jié)果
如上一小節(jié)的圖所示,在使用 Slice 方法提取字符串時(shí),絕對(duì)沒(méi)有分配。對(duì)于每個(gè)基準(zhǔn)測(cè)試方法,都會(huì)生成一行結(jié)果數(shù)據(jù)。因?yàn)橛袃蓚€(gè)基準(zhǔn)測(cè)試方法,所以有兩行基準(zhǔn)測(cè)試結(jié)果數(shù)據(jù)?;鶞?zhǔn)測(cè)試結(jié)果顯示了平均執(zhí)行時(shí)間、 Gen0集合和分配的內(nèi)存。從基準(zhǔn)測(cè)試結(jié)果中可以明顯看出,Span 比 Substring 方法快7.5倍以上(譯者圖中的結(jié)果是9倍)。
Span 限制
Span<T>
?是僅堆棧的,這意味著它不適合在堆上存儲(chǔ)對(duì)緩沖區(qū)的引用,例如在執(zhí)行異步調(diào)用的例程中。它不在托管堆中分配,而是在堆棧中分配,并且它不支持裝箱以防止升級(jí)到托管堆。不能將?Span<T>
?用作泛型類型,但可以將其用作 ref 結(jié)構(gòu)中的字段類型。不能將?Span<T>
?賦給動(dòng)態(tài)類型、對(duì)象類型或任何其他接口類型的變量。不能在引用類型中使用?Span<T>
?作為字段,也不能跨等待和產(chǎn)生邊界使用它。此外,由于?Span<T>
?不繼承 IEnumable,因此不能對(duì)其使用 LINQ。
需要注意的是,類中不能有?Span<T>
?字段,不能創(chuàng)建?Span<T>
?數(shù)組,也不能包含?Span<T>
?實(shí)例。注意,?Span<T>
?和Memory<T>?
都沒(méi)有實(shí)現(xiàn)?IEnumable<T>
?,因此,開(kāi)發(fā)者將無(wú)法使用 LINQ 與這兩者操作。但是,開(kāi)發(fā)者可以利用 SpanLinq 、 NetFabric、Hyperlinq 庫(kù)來(lái)繞過(guò)這個(gè)限制。
結(jié)論
在本文中,作者研究了?Span<T>
?和?Memory<T>
?的特性和優(yōu)點(diǎn),以及如何在應(yīng)用程序中實(shí)現(xiàn)它們。作者還討論了一個(gè)實(shí)際場(chǎng)景,其中可以使用 Span?來(lái)提高字符串處理性能。請(qǐng)注意,Span<T>
?比?Memory<T>
?更多才多藝,性能也更好,但它并不能完全取代它。
原文鏈接:https://www.cnblogs.com/whuanle/p/16607582.html
相關(guān)推薦
- 2022-09-14 Go語(yǔ)言中序列化與反序列化示例詳解_Golang
- 2022-07-06 C++如何切割String對(duì)象的方法_C 語(yǔ)言
- 2023-07-26 webpack優(yōu)化代碼運(yùn)行之Code split
- 2022-01-07 event的srcelement和target
- 2022-06-18 Redis官方可視化工具RedisInsight的安裝使用詳細(xì)教程(功能強(qiáng)大)_Redis
- 2022-04-03 .NET?Core配置連接字符串和獲取數(shù)據(jù)庫(kù)上下文實(shí)例_實(shí)用技巧
- 2023-05-23 golang中的單引號(hào)轉(zhuǎn)義問(wèn)題_Golang
- 2022-12-24 Python創(chuàng)建增量目錄的代碼實(shí)例_python
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過(guò)濾器
- Spring Security概述快速入門(mén)
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支