最近在学习.NET开源IO的源码,发现用的比较多的一个特性,那就是Span<T>,那么Span特性具体是做什么的呢?
Span<T>
表示任意内存的连续区域。 Span<T>
实例通常用于保存数组或数组的一部分的元素。但是,与数组不同, Span<T>
实例可以指向堆栈上托管的内存、本机内存或内存。Span<Byte>
var array = new byte[1000000];
var arraySpan = new Span<byte>(array);//装载到span
byte data = 0;
for (int i = 0; i < arraySpan.Length; i++)
{
arraySpan[i] = data++;
}
int arraySum = 0;
foreach (var item in arraySpan)
{
arraySum += item;
}
Console.WriteLine("span结果是: {0}", arraySum);
//span结果是: 127493856
var native = Marshal.AllocHGlobal(100000);
Span<byte> nativeSpan;
unsafe
{
nativeSpan = new Span<byte>(native.ToPointer(), 100);
}
byte data = 0;
for (int ctr = 0; ctr < nativeSpan.Length; ctr++)
nativeSpan[ctr] = data++;
int nativeSum = 0;
foreach (var value in nativeSpan)
nativeSum += value;
Console.WriteLine("span结果是: {0}", nativeSum );
Marshal.FreeHGlobal(native);
//span结果是: 127493856
Span<T> 和切片与数组
Span<T>
包括两个重载 Slice 的方法,该方法从从指定索引开始的当前范围外形成切片。这使得可以将数据 Span<T>
视为一组逻辑区块,这些区块可由数据处理管道的一部分根据需要进行处理,且性能影响最小。相当于Substring,但Substring会创建一个新的字符串,并且用原来的字符串复制到新的字符串。
我们来对比一下性能:
var str = "love.NET";
var strSpan = str.AsSpan();
var sw = new Stopwatch();
sw.Start();
for (var j = 0; j < 100000; j++)
{
var strs= strSpan.Slice(4);
}
sw.Stop();
Console.WriteLine("Span耗时:" + sw.ElapsedMilliseconds);
// Span<T> 操作 ReadOnlySpan<T>来消除此分配和复制操作
sw.Restart();
for (var j = 0; j < 100000; j++)
{
var strs = str.Substring(4);
}
sw.Stop();
Console.WriteLine("Substring耗时:" + sw.ElapsedMilliseconds);
看出差距了吧。
Span<T>还可以包装整个数组,而且还支持整个数组切片,因此可以指定数组的连续范围。我们来看效果。
// 字符串数组
var strs = new string[] { "java", "c++", "c#", "go","python" };
var slice = new Span<string>(strs, 2, 2);
for (int ctr = 0; ctr < slice.Length; ctr++)
slice[ctr] += "牛";
// Examine the original array values.
foreach (var value in strs)
Console.Write($"{value} ");
//结果如下:
