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