深入了解 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<T> 是 C# 中一个强大且高效的工具,可以帮助我们更好地进行

深入了解 C# Span:高性能内存操作的利器的更多相关文章

  1. span<T>之高性能字符串操作实测

    .net中的字符串操作性能问题由来已久,幸运的是微软推出了span<T>高性能指针操作封装工具类.这个类到底有多高的性能呢?网上传言反正很高,但是实际上在网上很难找到合适的测试实例,这让本 ...

  2. 如何使用T-SQL备份还原数据库及c#如何调用执行? C#中索引器的作用和实现。 jquery控制元素的隐藏和显示的几种方法。 localStorage、sessionStorage用法总结 在AspNetCore中扩展Log系列 - 介绍开源类库的使用(一) span<T>之高性能字符串操作实测

    如何使用T-SQL备份还原数据库及c#如何调用执行? 准备材料:Microsoft SQL Server一部.需要还原的bak文件一只 一.备份 数据库备份语句:user master backup ...

  3. 支撑百万级并发,Netty如何实现高性能内存管理

    Netty作为一款高性能网络应用程序框架,实现了一套高性能内存管理机制 通过学习其中的实现原理.算法.并发设计,有利于我们写出更优雅.更高性能的代码:当使用Netty时碰到内存方面的问题时,也可以更高 ...

  4. JUC并发编程与高性能内存队列disruptor实战-下

    并发理论 JMM 概述 Java Memory Model缩写为JMM,直译为Java内存模型,定义了一套在多线程读写共享数据时(成员变量.数组)时,对数据的可见性.有序性和原子性的规则和保障:JMM ...

  5. java 21-11 数据输入、输出流和内存操作流

    IO数据流: 可以读写基本数据类型的数据 数据输入流:DataInputStream DataInputStream(InputStream in)   数据输出流:DataOutputStream ...

  6. 【转】《深入理解计算机系统》C程序中常见的内存操作有关的典型编程错误

    原文地址:http://blog.csdn.net/slvher/article/details/9150597 对C/C++程序员来说,内存管理是个不小的挑战,绝对值得慎之又慎,否则让由上万行代码构 ...

  7. c++ void,内存操作函数

    void的含义 void的字面意思是“无类型”, void * 则为“无类型指针”, void * 可以指向任何类型的数据 void几乎只有“注释”和限制程序的作用,因为从来没有人会定义一个void变 ...

  8. java基础知识回顾之javaIO类--内存操作流ByteArrayInputStream和ByteArrayOutputSteam(操作字节数组)

    直接看代码: package cn.itcast.io.p6.bytestream; import java.io.ByteArrayInputStream; import java.io.ByteA ...

  9. Java API —— IO流(数据操作流 & 内存操作流 & 打印流 & 标准输入输出流 & 随机访问流 & 合并流 & 序列化流 & Properties & NIO)

    1.操作基本数据类型的流     1) 操作基本数据类型 · DataInputStream:数据输入流允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型.应用程序可以使用数据输出 ...

  10. 【C++基础】内存操作 getMemory改错

    内存操作的考察点:①指针 ②变量生存期及作用范围 ③动态内存申请和释放 笔试题************************************************************* ...

随机推荐

  1. Jetpack Compose(6)——动画

    目录 一.低级别动画 API 1.1 animate*AsState 1.2 Animatable 1.3 Transition 动画 1.3.1 updateTransition 1.3.2 cre ...

  2. FPGA最大工作频率教程

    FPGA最大工作频率教程 1.  Quartus的时序分析 作为编译过程的一部分,Quartus对布局布线的电路做时序分析.在编译报告里,展开"Timing Analyzer",这 ...

  3. 使用AI在原神里自动钓鱼,扫描Git仓库泄露的密码 【蛮三刀酱的Github周刊第三期】

    大家好,这里是的Github精彩项目分享周刊,我是每周都在搬砖的蛮三刀酱. 我会从Github热门趋势榜里选出 高质量.有趣,牛B 的开源项目进行分享. 榜单取自实时Github Trending排行 ...

  4. vim 使用clang-format 格化C/C++/Java/JavaScript

    vim 使用clang-format 格化C/C++/Java/JavaScript 参考信息 官方参考https://clang.llvm.org/docs/ClangFormat.html 安装 ...

  5. R1_ES知识图谱

    业务量增加,优化..优化... 学习... 学习..... 阮一鸣,eBay Pronto 平台技术负责人,管理了 eBay 内部上百个 Elasticsearch 集群,数据规模超过 4000 节点 ...

  6. 2024-05-04:用go语言,给定一个起始索引为0的字符串s和一个整数k。 要进行分割操作,直到字符串s为空: 选择s的最长前缀,该前缀最多包含k个不同字符; 删除该前缀,递增分割计数。如果有剩余

    2024-05-04:用go语言,给定一个起始索引为0的字符串s和一个整数k. 要进行分割操作,直到字符串s为空: 选择s的最长前缀,该前缀最多包含k个不同字符: 删除该前缀,递增分割计数.如果有剩余 ...

  7. 【python爬虫案例】爬取微博任意搜索关键词的结果,以“唐山打人”为例

    目录 一.爬取目标 二.展示爬取结果 三.讲解代码 四.同步视频 4.1 演示视频 4.2 讲解视频 五.附:完整源码 一.爬取目标 大家好,我是马哥. 今天分享一期python爬虫案例,爬取目标是新 ...

  8. 08. C语言函数

    [函数基础] 函数用于将程序代码分类管理,实现不同功能的代码放在不同函数内,一个函数等于一种功能,其它函数可以调用本函数执行. C语言规定所有的指令数据必须定义在函数内部,比如之前介绍的程序执行流程控 ...

  9. GDB 中内存打印命令

    GDB 中使用 "x" 命令来打印内存的值,格式为 "x/nfu addr".含义为以 f 格式打印从 addr 开始的 n 个长度单元为 u 的内存值.参数具 ...

  10. make编译报错:fatal error: filesystem: 没有那个文件或目录 #include <filesystem>

    报错: fatal error: filesystem: 没有那个文件或目录 #include(filesystem) 解决方法一: 修改头文件 #include <experimental/f ...