深入了解 C# Span:高性能内存操作的利器

深入了解 C# Span:高性能内存操作的利器

在 C# 7.2 中引入的 Span<T> 类型为我们提供了一种高效且安全地对内存进行操作的方式。Span<T> 是一个轻量级的结构体,用于表示一段连续的内存区域,可以避免不必要的内存分配和拷贝,提高代码的性能和效率。

什么是 Span?

Span<T> 是一个用于表示连续内存区域的结构体,它提供了一组方法来对内存进行读写操作,而无需额外的内存分配或拷贝。通过 Span<T>,我们可以直接操作数组、堆栈、堆等内存区域,从而提高代码的性能和效率。

主要特点:

  • 零分配:Span 提供了零分配的内存操作方式,避免了在一些情况下不必要的内存分配和拷贝。这对于性能敏感的应用程序非常有益。

  • 安全性:Span 提供了安全的内存访问方式,确保在访问内存时不会发生越界访问或其他不安全操作。

  • 高效性:通过 Span,可以直接对内存进行读写操作,避免了额外的内存拷贝和装箱操作,提高了代码的性能和效率。

  • 可变性:Span 是可变的,可以修改指向的内存中的数据,从而实现高效的数据操作和处理。

应用场景

  • 数组操作:Span 可以直接操作数组中的元素,而不需要额外的内存拷贝,适用于需要高效处理数组数据的场景。

  • 字符串处理:Span 可以用于高效地处理字符串,例如字符串拆分、搜索、替换等操作,避免不必要的字符串分配和拷贝。

  • 内存池管理:Span 可以与内存池一起使用,提高内存分配和释放的效率,减少 GC 压力。

  • 文件 I/O 操作:在文件读写等 I/O 操作中,Span 可以减少内存拷贝开销,提高读写效率。

  • 网络编程:在网络编程中,Span 可以用于处理网络数据包、解析协议等操作,提高网络数据处理的效率。

  • 异步编程:Span 可以与异步编程结合使用,提供高效的数据处理方式,例如在处理大量数据时减少内存拷贝开销。

  • 性能优化:在需要高性能的应用程序中,可以使用 Span 来避免不必要的内存分配和拷贝,提高代码的执行效率。

使用 Span 进行内存操作

// 创建一个包含整型数据的数组
int[] array = new int[] { 1, 2, 3, 4, 5 };

// 使用 Span 对数组进行操作
Span<int> span = array.AsSpan();
span[2] = 10; // 修改第三个元素的值为 10

// 输出修改后的数组
foreach (var num in array)
{
    Console.WriteLine(num);
}

通过以上示例,我们可以看到如何使用 Span<T> 对数组进行直接操作,并修改其中的元素值。

案例一: 借助Span字符串转int和float

public static int ParseToInt(this ReadOnlySpan<char> rspan)
    {
        Int16 sign = 1;
        int num = 0;
        UInt16 index = 0;
        if (rspan[0].Equals('-')){
            sign = -1; index = 1;
        }
        for (int idx = index; idx < rspan.Length; idx++){
            char c = rspan[idx];
            num = (c - '0') + num * 10;
        }
        return num * sign;
    }

    public static float ParseToFloat(this ReadOnlySpan<char> span)
    {
        bool isNegative = false;
        int startIndex = 0;

        // 判断是否为负数
        if (span.Length > 0 && span[0] == '-')
        {
            isNegative = true;
            startIndex = 1;
        }

        bool hasDecimal = false;
        float result = 0;
        float decimalPlace = 0.1f;

        for (int i = startIndex; i < span.Length; i++)
        {
            char c = span[i];

            if (c == '.')
            {
                hasDecimal = true;
                continue;
            }

            if (c < '0' || c > '9')
            {
                Debug.LogError("Invalid digit in input");
            }

            if (!hasDecimal)
            {
                result = result * 10 + (c - '0');
            }
            else
            {
                result += (c - '0') * decimalPlace;
                decimalPlace /= 10;
            }
        }

        if (isNegative)
        {
            result *= -1;
        }

        return result;
    }

案例二:字符串分割转换

 public void TestSpanParse()
    {
        List<Vector3> vertices = new List<Vector3>();
        List<Vector3> colors = new List<Vector3>();
        string[] lines = new string[lineCount];
        for (int i = 0; i < lineCount; i++)
        {
            lines[i] = $"{i} {i + 0.1f} {i + 0.5f} {i} {i + 0.1f} {i + 0.5f}";
        }
        
        using (CustomTimer ct = new CustomTimer("TestSpanParse", -1))
        {
            foreach (var line in lines)
            {
                ReadOnlySpan<char> sp = line.AsSpan();
                int spaceIndex1 = sp.IndexOf(' ');
                int spaceIndex2 = sp.Slice(spaceIndex1 + 1).IndexOf(' ');
                int spaceIndex3 = sp.Slice(spaceIndex1 +spaceIndex2 + 2).IndexOf(' ');
                int spaceIndex4 = sp.Slice(spaceIndex1 +spaceIndex2+spaceIndex3 + 3).IndexOf(' ');
                int spaceIndex5 = sp.Slice(spaceIndex1 +spaceIndex2+spaceIndex3 +spaceIndex4 + 4).IndexOf(' ');
                var xSp = (sp.Slice(0, spaceIndex1));
                var ySp = sp.Slice(spaceIndex1 + 1, spaceIndex2);
                var zSp = sp.Slice(spaceIndex1 +spaceIndex2 + 2, spaceIndex3);
                var rSp= sp.Slice(spaceIndex1 +spaceIndex2+spaceIndex3 + 3, spaceIndex4);
                var gSp = sp.Slice(spaceIndex1 +spaceIndex2+spaceIndex3 +spaceIndex4 + 4, spaceIndex5);
                var bSp = sp.Slice(spaceIndex1 +spaceIndex2+spaceIndex3 +spaceIndex4 + spaceIndex5+5);
                vertices.Add(new Vector3(float.Parse(xSp), float.Parse(ySp), float.Parse(zSp)));
                colors.Add(new Vector3(float.Parse(rSp), float.Parse(gSp), float.Parse(bSp))); 
            }
            Debug.Log(vertices.Last().ToString()+" "+colors.Last().ToString());    
        }
        
        using (CustomTimer ct = new CustomTimer("TestParse", -1))
        {
            foreach (var line in lines)
            {
                var split = line.Split(' ');
                vertices.Add(new Vector3(float.Parse(split[0]), float.Parse(split[1]), float.Parse(split[2])));
                colors.Add(new Vector3(float.Parse(split[3]), float.Parse(split[4]), float.Parse(split[5])));
            }
            Debug.Log(vertices.Last().ToString()+" "+colors.Last().ToString());    
        }
    }

性能测试结果:150000次调用

案例三:字符串处理

using UnityEngine;

public class StringProcessor : MonoBehaviour
{
    void Start()
    {
        string text = "Hello, World!";
        Span<char> span = text.AsSpan();

        Debug.Log(ReverseString(span)); // 输出 "!dlroW ,olleH"
    }

    string ReverseString(Span<char> span)
    {
        char[] reversed = new char[span.Length];
        for (int i = 0; i < span.Length; i++)
        {
            reversed[i] = span[span.Length - 1 - i];
        }
        return new string(reversed);
    }
}

案例四:优化网络数据处理

using UnityEngine;
using System;
using System.Net.Sockets;

public class NetworkProcessor : MonoBehaviour
{
    private Socket socket;

    void Start()
    {
        // 假设socket已经连接
        byte[] buffer = new byte[1024];
        int bytesRead = socket.Receive(buffer);
        ProcessData(buffer, bytesRead);
    }

    void ProcessData(byte[] data, int length)
    {
        Span<byte> dataSpan = new Span<byte>(data, 0, length);

        // 处理数据
        // 例如:解析消息,处理命令等
        for (int i = 0; i < dataSpan.Length; i++)
        {
            Debug.Log(dataSpan[i]);
        }
    }
}

案例五:图像处理

using UnityEngine;
using System;

public class ImageProcessor : MonoBehaviour
{
    public Texture2D texture;

    void Start()
    {
        Color32[] pixels = texture.GetPixels32();
        ProcessImage(pixels);

        texture.SetPixels32(pixels);
        texture.Apply();
    }

    void ProcessImage(Color32[] pixels)
    {
        Span<Color32> pixelSpan = new Span<Color32>(pixels);

        for (int i = 0; i < pixelSpan.Length; i++)
        {
            Color32 pixel = pixelSpan[i];
            pixel.r = (byte)(255 - pixel.r); // 反转红色分量
            pixel.g = (byte)(255 - pixel.g); // 反转绿色分量
            pixel.b = (byte)(255 - pixel.b); // 反转蓝色分量
            pixelSpan[i] = pixel;
        }
    }
}

span不需要暂留,必须快取快用快放,否则就不要使用span。

结语

总的来说, Span是C#中一个非常实用的结构类型,它在内存管理、性能优化和安全性方面都有很好的表现。随着C#的不断发展,Span将在更多的场景中发挥重要作用。

posted @   世纪末の魔术师  阅读(1399)  评论(2编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
  1. 1 ありがとう··· KOKIA
ありがとう··· - KOKIA
00:00 / 00:00
An audio error has occurred.

作词 : KOKIA

作曲 : KOKIA

编曲 : 日向敏文

作词 : KOKIA

作曲 : KOKIA

誰もが気付かぬうちに

誰もが気付かぬうちに

何かを失っている

フッと気付けばあなたはいない

思い出だけを残して

せわしい時の中

言葉を失った人形達のように

街角に溢れたノラネコのように

声にならない叫びが聞こえてくる

もしも もう一度あなたに会えるなら

もしも もう一度あなたに会えるなら

たった一言伝えたい

ありがとう

ありがとう

時には傷つけあっても

時には傷つけあっても

あなたを感じていたい

思い出はせめてもの慰め

いつまでもあなたはここにいる

もしも もう一度あなたに会えるなら

もしも もう一度あなたに会えるなら

たった一言伝えたい

ありがとう

ありがとう

もしも もう一度あなたに会えるなら

もしも もう一度あなたに会えるなら

たった一言伝えたい

もしも もう一度あなたに会えるなら

たった一言伝えたい

ありがとう

ありがとう

時には傷つけあっても

時には傷つけあっても

あなたを感じてたい

点击右上角即可分享
微信分享提示