C#性能优化实战:Span

时间:10/23/2024 18:50:52   作者:ChenReal    阅读:33

最近一段时间,我在做Code Review和优化重构的工作。我发现在很多代码场景下,如果使用Span<T>之后,无往不利,代码的性能提升显著,神挡杀神佛挡杀佛。迫不及待地给大家安利一下~

我将拿出其中两个案例,给大家介绍一下如何用Span<T>来优化代码,达到性能提升的效果。

在开始我的表演之前,还是先简单介绍一下Span是什么玩意吧。下面这一段话是网上copy过来的,如果看不懂的话,直接去搜索一下,或者忽略它直接看代码就行了!

什么是Span<T>

Span<T> 是在 C# 7.2 后开始出现的语法,它为我们提供了一种高效且安全地对内存进行读写操作。通过 Span<T>,我们可以直接操作数组、堆栈、堆等内存区域,可以避免不必要的内存分配和拷贝,从而提高代码的性能和效率。

Span<T>有哪些应用场景?

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

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

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

  • 文件 I/O 操作: 在文件读写等 I/O 操作中,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.Write(num);
}
// 输出结果:121045

通过以上简单的代码示例,我们可以看到如何使用 Span<T> 对数组进行直接操作,并修改其中的元素值。下面继续,举几个我在实际开发场景中所用到的案例,通过这些示例,希望能够让大家更进一步的了解Span<T>的实际运用。并且,我会拿出优化前的代码来进行性能的对比,这样大家就能够直观感受到Span<T>的威力了。

案例一:字符串的拆分及数组类型的转换

我需要将以下的字符串中逗号连接的数字才分出来,并且转成int数组。

private const string StringForSplit = "666,747,200,468,471,395,942,589,87,353,456,536,772,599,552,338,553,925,532,383,668,96,61,125,621,917,774,146,54,885";

1、优化前的代码:

public int[] TestSplitString()
{
    return StringForSplit.Split(',').Select(x => Convert.ToInt32(x)).ToArray();
}

2、Span<T>优化的代码:

public int[] TestSplitStringWithSpan()
{
    var span = StringForSplit.AsSpan();
    var separator = ",".AsSpan();
    var sepLen = separator.Length;
    var index = -1;
    var result = new List<int>();
    do
    {
        index = span.IndexOf(separator);
        if (index == -1)
        {
            result.Add(int.Parse(span));
        }
        else
        {
            var value = span.Slice(0, index);
            result.Add(int.Parse(value));
            span = span.Slice(index + sepLen);
        }
    } 
    while (index != -1);
    return result.ToArray();
}

3、性能比对

image.png 从上图数据,我们可以看出代码优化效果非常明显,内存节省了64%,运行速度提升了30%。

案例二:提取HTML代码中的文本内容

我需要将以下的HTML代码中的Country信息提取出来。

const string HtmlCode = @"<html>
<head>
    <meta charset=""utf-8"">
    <title>Test Page</title>
</head>
<body>
    <header>...</header>
    <main>
        <article>
            <h3>Country list</h3>
            <ul>
                <li><span>Australia</span></li>
                <li><span>Brazil</span></li>
                <li><span>Canada</span></li>
                <li><span>China</span></li>
                <li><span>France</span></li>
                <li><span>Germany</span></li>
                <li><span>Japan</span></li>
                <li><span>South Korea</span></li>
                <li><span>United States</span></li>
                <li><span>United Kingdom</span></li>
            </ul>
        </article>
    </main>
    <footer>...</footer>
</body>
</html>";

1、优化前的代码:

解决这个问题,大家首先会想到的是用正则表达式。没错,之前写代码的家伙也是这么想的。

public string[] TestFilterWithRegularExpression()
{
    const string REGX_PTN = @"<li><span>(.*)<\/span><\/li>";
    var matches = Regex.Matches(HtmlCode, REGX_PTN);
    var result = new List<string>();
    foreach (Match match in matches)
    {
        result.Add(match.Groups[1].Value);
    }
    return result.ToArray();
}

2、Span<T>优化的代码:

public string[] TestFilterWithSpan()
{
    const string countryBegin = "<h3>Country list</h3>";
    const string countryEnd = "</ul>";
    const string startTag = "<li><span>";
    const string endTag = "</span>";

    var span = HtmlCode.AsSpan();
    var countrySpan = countryBegin.AsSpan();
    int index = span.IndexOf(countrySpan);
    span = span.Slice(index + countrySpan.Length);
    index = span.IndexOf(countryEnd.AsSpan());
    span = span.Slice(0, index);


    var startTagSpan = startTag.AsSpan();
    var endTagSpan = endTag.AsSpan();
    var startTagLen = startTagSpan.Length;
    var endTagLen = endTagSpan.Length;

    var result = new List<string>();
    while (true)
    {
        index = span.IndexOf(startTagSpan);
        var endIndex = span.IndexOf(endTagSpan);
        if (index == -1 || endIndex ==-1) break;
        var value = span.Slice(index + startTagLen, endIndex -index-startTagLen);
        result.Add(value.ToString());
        span = span.Slice(endIndex + endTagLen);
    }

    return result.ToArray();
}

3、性能比对

image.png

这一组数据对比下来,用“惊艳”两个来形容也不为过吧?Span<T>优化过后的代码,无论在运行速度,还是内存使用方面的指标,都是数倍甚至10倍的提升!

总结

总的来说, Span是C#中一个非常实用的结构类型,它在内存管理、性能优化和安全性方面都有很好的表现。对代码性能有追求的朋友不妨来试试吧。通过两个案例的代码横向比较,相信大家已经对它产生兴趣了。

 

评论
0/200