欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C#中的高性能內(nèi)存操作的利器:Span<T>和Memory<T>

 更新時間:2025年08月02日 10:32:51   作者:子丶不語  
在.NET開發(fā)中,內(nèi)存管理一直是影響性能的關鍵因素,.NET Core 2.1引入Span和Memory優(yōu)化內(nèi)存管理,減少分配與復制開銷,Span棧分配、無GC壓力,適用于同步高性能場景;Memory堆分配、支持異步操作,適合跨方法傳遞與長期存儲,合理選擇可提升代碼效率與可靠性

在.NET開發(fā)中,內(nèi)存管理一直是影響性能的關鍵因素。傳統(tǒng)的字符串處理、數(shù)組操作等往往伴隨著大量的內(nèi)存分配和復制操作,這些不必要的開銷在高性能場景下尤為明顯。

為了解決這個問題,.NET Core 2.1引入了Span和Memory這兩個強大的類型,它們能夠:

  • 顯著減少內(nèi)存分配
  • 提升數(shù)據(jù)操作性能
  • 安全地訪問連續(xù)內(nèi)存區(qū)域
  • 支持多種內(nèi)存來源的統(tǒng)一操作

Span:棧上分配的高性能利器

Span的本質(zhì)

Span是一個棧分配的結(jié)構(gòu)體(值類型),它提供了一種不需要額外內(nèi)存分配就能操作連續(xù)內(nèi)存區(qū)域的方法。

int[] numbers = { 1, 2, 3, 4, 5 };
Span<int> span = numbers; 
span[0] = 10; 
Console.WriteLine(numbers[0]);

注意:數(shù)組堆上分配的引用類型,與Span還是有區(qū)別的,Span無GC壓力。

Span與字符串處理

傳統(tǒng)的字符串處理方法如Substring()會創(chuàng)建新的字符串實例,而使用Span可以避免這種額外的內(nèi)存分配:

using System;

class Program
{
    static void Main()
    {
        string orderData = "ORD-12345-AB: 已發(fā)貨";

        // 傳統(tǒng)方式 - 創(chuàng)建新的字符串對象
        string orderId1 = orderData.Substring(0, 11); // 分配新內(nèi)存
        string status1 = orderData.Substring(13);     // 再次分配新內(nèi)存

        // 使用Span<T> - 不創(chuàng)建新的字符串對象
        ReadOnlySpan<char> dataSpan = orderData.AsSpan();
        ReadOnlySpan<char> orderId2 = dataSpan.Slice(0, 11); // 不分配新內(nèi)存
        ReadOnlySpan<char> status2 = dataSpan.Slice(13);     // 不分配新內(nèi)存

        // 必要時才將Span轉(zhuǎn)換為string
        Console.WriteLine($"訂單號: {orderId2.ToString()}");
        Console.WriteLine($"狀態(tài): {status2.ToString()}");
    }
}

使用stackalloc與Span

Span可以直接與棧上分配的內(nèi)存一起使用,避免堆分配的開銷:

using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace AppSpanMemory
{
    internal class Program
    {
        static unsafe void Main()
        {
            Span<int> stackNums = stackalloc int[100];

            for (int i = 0; i < stackNums.Length; i++)
            {
                stackNums[i] = i * 10;
            }

            // 獲取Span起始位置的指針
            void* ptr = Unsafe.AsPointer(ref MemoryMarshal.GetReference(stackNums));

            Console.WriteLine($"Span內(nèi)存地址: 0x{(ulong)ptr:X}");

            // 打印前10個元素
            var firstTen = stackNums.Slice(0, 10);
            foreach (var n in firstTen)
            {
                Console.Write($"{n} ");
            }
            Console.ReadKey();
        }
    }
}

Span的關鍵特性

  • 零內(nèi)存分配操作數(shù)據(jù)時不創(chuàng)建額外的內(nèi)存對象
  • 類型安全提供類型檢查,避免類型轉(zhuǎn)換錯誤
  • 可用于多種內(nèi)存來源數(shù)組、固定大小緩沖區(qū)、棧分配內(nèi)存、非托管內(nèi)存等
  • 性能優(yōu)勢適用于高性能計算和數(shù)據(jù)處理場景
  • 限制只能在同步方法中使用,不能作為類的字段

Memory:異步操作的理想選擇

Memory的定位

Memory是Span的堆分配版本,主要用于支持異步操作場景。

// Memory<T>的基本使用
Memory<int> memory = new int[] { 1, 2, 3, 4, 5 };
Span<int> spanFromMemory = memory.Span; // 從Memory獲取Span視圖
spanFromMemory[0] = 20;
Console.WriteLine(memory.Span[0]);

Memory與異步文件操作

Memory在處理異步I/O操作時特別有用:

using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace AppSpanMemory
{
    internal class Program
    {
        static async Task Main()
        {
            // 創(chuàng)建一個4KB的緩沖區(qū)
            byte[] buffer = new byte[4096];
            Memory<byte> memoryBuffer = buffer; 

            using FileStream fileStream = new FileStream("bigdata.dat", FileMode.Open, FileAccess.Read);
            int bytesRead = await fileStream.ReadAsync(memoryBuffer);

            if (bytesRead > 0)
            {
                Memory<byte> actualData = memoryBuffer.Slice(0, bytesRead);
                ProcessData(actualData.Span);
            }

            Console.WriteLine($"讀取了 {bytesRead} 字節(jié)的數(shù)據(jù)");
        }

        static void ProcessData(Span<byte> data)
        {
            Console.WriteLine($"前10個字節(jié): {BitConverter.ToString(data.Slice(0, Math.Min(10, data.Length)).ToArray())}");
        }
    }
}

Memory的關鍵特性

  • 異步友好可以在異步方法中使用
  • 不綁定執(zhí)行上下文可以在方法之間傳遞
  • 可作為類字段可以存儲在類中長期使用
  • 性能略低相比Span有輕微的性能開銷
  • 更靈活可用于更多場景

Span與Memory的對比選擇

特性

Span<T>

Memory<T>

分配位置

異步支持

不支持

支持

性能表現(xiàn)

更高

稍低

適用場景

同步高性能操作

異步操作、跨方法傳遞

可否作為字段

不可以

可以

生命周期

方法范圍內(nèi)

可長期存在

實戰(zhàn)應用場景

高性能字符串解析

using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace AppSpanMemory
{
    internal class Program
    {
        static async Task Main()
        {
            string csvLine = "張三,30,北京市海淀區(qū),軟件工程師";
            ParseCsvLine(csvLine.AsSpan());
        }

        public static void ParseCsvLine(ReadOnlySpan<char> line)
        {
            int start = 0;
            int fieldIndex = 0;

            for (int i = 0; i < line.Length; i++)
            {
                if (line[i] == ',')
                {
                    // 不創(chuàng)建新字符串
                    ReadOnlySpan<char> field = line.Slice(start, i - start);
                    ProcessField(fieldIndex, field);

                    start = i + 1;
                    fieldIndex++;
                }
            }

            // 處理最后一個字段
            if (start < line.Length)
            {
                ReadOnlySpan<char> lastField = line.Slice(start);
                ProcessField(fieldIndex, lastField);
            }
        }

        private static void ProcessField(int index, ReadOnlySpan<char> field)
        {
            Console.WriteLine($"字段 {index}: '{field.ToString()}'");
        }

    }
}

二進制數(shù)據(jù)處理

using System;
using System.Buffers.Binary;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;

namespace AppSpanMemory
{
    internal class Program
    {
        static async Task Main()
        {
            string csvLine = "張三,30,北京市海淀區(qū),軟件工程師";

            byte[] payloadBytes = Encoding.UTF8.GetBytes(csvLine);

            // 頭部4字節(jié) + 數(shù)據(jù)長度4字節(jié) + 數(shù)據(jù)體
            byte[] fileData = new byte[4 + 4 + payloadBytes.Length];

            // 寫入頭部標識 "DATA"
            fileData[0] = (byte)'D';
            fileData[1] = (byte)'A';
            fileData[2] = (byte)'T';
            fileData[3] = (byte)'A';

            // 寫入數(shù)據(jù)長度(小端)
            BinaryPrimitives.WriteInt32LittleEndian(fileData.AsSpan(4, 4), payloadBytes.Length);

            // 寫入數(shù)據(jù)體
            payloadBytes.CopyTo(fileData.AsSpan(8));

            // 傳入文件字節(jié)數(shù)據(jù)的只讀切片
            ProcessBinaryFile(fileData);
        }

        public static void ProcessBinaryFile(ReadOnlySpan<byte> data)
        {
            // [4字節(jié)頭部標識][4字節(jié)數(shù)據(jù)長度][實際數(shù)據(jù)]
            if (data.Length < 8)
            {
                thrownew ArgumentException("數(shù)據(jù)格式不正確");
            }

            // 檢查頭部標識"DATA"
            ReadOnlySpan<byte> header = data.Slice(0, 4);
            if (!(header[0] == 'D' && header[1] == 'A' && header[2] == 'T' && header[3] == 'A'))
            {
                thrownew ArgumentException("無效的文件頭");
            }

            // 讀取數(shù)據(jù)長度 (小端字節(jié)序)
            int dataLength = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4));

            // 確保數(shù)據(jù)完整
            if (data.Length < 8 + dataLength)
            {
                thrownew ArgumentException("數(shù)據(jù)不完整");
            }

            // 獲取實際數(shù)據(jù)部分
            ReadOnlySpan<byte> payload = data.Slice(8, dataLength);

            Console.WriteLine($"有效載荷大小: {payload.Length} 字節(jié)");
            Console.WriteLine($"前10個字節(jié): {BitConverter.ToString(payload.Slice(0, Math.Min(10, payload.Length)).ToArray())}");
        }

    }
}

使用注意事項

安全使用Span的建議

  • 不要嘗試將Span作為字段存儲
  • 不要將Span用于異步方法
  • 避免將Span裝箱(boxing)
  • 小心Span的生命周期管理,特別是使用stackalloc時
  • 使用ReadOnlySpan表示不需要修改的數(shù)據(jù)

Memory的最佳實踐

  • 優(yōu)先考慮ReadOnlyMemory而非Memory(當不需要修改數(shù)據(jù)時)
  • 在異步操作中使用Memory替代數(shù)組
  • 在需要長期保留引用時使用Memory而非Span
  • 需要操作時才調(diào)用.Span屬性,不要過早轉(zhuǎn)換

兼容性與平臺支持

Span和Memory支持情況:

  • .NET Core 2.1及更高版本
  • .NET Standard 2.1
  • .NET 5/6/7/8及以后版本
  • 不完全支持.NET Framework,但可通過System.Memory NuGet包獲得部分支持

總結(jié)

Span和Memory是C#中處理高性能內(nèi)存操作的強大工具,它們能夠:

  1. 減少內(nèi)存分配和GC壓力通過避免不必要的內(nèi)存分配和復制
  2. 提高性能特別是在處理大量數(shù)據(jù)和頻繁字符串操作時
  3. 保持類型安全避免了使用unsafe代碼和指針操作的風險
  4. 簡化代碼提供了直觀的API來處理連續(xù)內(nèi)存區(qū)域

在實際開發(fā)中,記住這些簡單的選擇規(guī)則:

  • 對于同步方法中的高性能操作,選擇Span
  • 對于異步方法或需要跨方法傳遞的場景,選擇Memory

掌握這兩個強大的工具,將幫助你編寫更高效、更可靠的C#代碼,特別是在處理大數(shù)據(jù)量、高性能要求的應用場景中。

相關文章

  • C# GDI在控件上繪圖的方法

    C# GDI在控件上繪圖的方法

    這篇文章主要介紹了C# GDI在控件上繪圖的方法,包括了常見的鼠標事件及繪圖操作,需要的朋友可以參考下
    2014-09-09
  • C#中的隊列Queue<T>與堆棧Stack<T>

    C#中的隊列Queue<T>與堆棧Stack<T>

    這篇文章介紹了C#中的隊列Queue<T>與堆棧Stack<T>,文中通過示例代碼介紹的非常詳細。對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-05-05
  • c#多進程通訊的實現(xiàn)示例

    c#多進程通訊的實現(xiàn)示例

    本文主要介紹了c#多進程通訊的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2022-05-05
  • c#?使用線程對串口serialPort進行收發(fā)數(shù)據(jù)(四種)

    c#?使用線程對串口serialPort進行收發(fā)數(shù)據(jù)(四種)

    本文主要介紹了c#?使用線程對串口serialPort進行收發(fā)數(shù)據(jù),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2022-07-07
  • C# 中的??操作符淺談

    C# 中的??操作符淺談

    (??) 用于如果類不為空值時返回它自身,如果為空值則返回之后的操作
    2013-04-04
  • C#編寫一個簡單記事本功能

    C#編寫一個簡單記事本功能

    這篇文章主要為大家詳細介紹了C#編寫一個簡單記事本功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-10-10
  • DataGridView實現(xiàn)點擊列頭升序和降序排序

    DataGridView實現(xiàn)點擊列頭升序和降序排序

    這篇文章介紹了DataGridView實現(xiàn)點擊列頭升序和降序排序的方法,文中通過示例代碼介紹的非常詳細。對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-02-02
  • C#實現(xiàn)微信跳一跳小游戲的自動跳躍助手開發(fā)實戰(zhàn)

    C#實現(xiàn)微信跳一跳小游戲的自動跳躍助手開發(fā)實戰(zhàn)

    前段時間微信更新了新版本后,帶來的一款H5小游戲“跳一跳”在各朋友圈里又火了起來,類似以前的“打飛機”游戲,這游戲玩法簡單,但加上了積分排名功能后,卻成了“裝逼”的地方,于是很多人花錢花時間的刷積分搶排名
    2018-01-01
  • C#中利用代理實現(xiàn)觀察者設計模式詳解

    C#中利用代理實現(xiàn)觀察者設計模式詳解

    學習模式注重精髓而非模板,本文為了便于說明假定了三方并對三方功能進行了劃分,實際應用并不拘泥于此。如果情況合適將數(shù)據(jù)(文檔)類設計為單件模式也是一種很不錯的選擇
    2014-01-01
  • 關于C#程序優(yōu)化的五十種方法

    關于C#程序優(yōu)化的五十種方法

    這篇文章主要介紹了C#程序優(yōu)化的五十個需要注意的地方,使用c#開發(fā)的朋友可以看下
    2013-09-09

最新評論