.NET/C# 使用 Span 为字符串处理提升性能

.NET Core 2.1 和 C# 7.2 带来了 Span 的原生支持,原本需要使用不安全代码操作的内存块现在可以使用安全的方式来完成。此前在性能和稳定性上需要有所取舍,而现在可以兼得了。


简单的例子

先来看一个字符串处理时使用 Span<T> 的最简单的例子:

using System;
using System.Text;

namespace Walterlv.Demo.StringSpan
{
    internal class Program
    {
        static void Main(string[] args)
        {
            var text = "https://walterlv.github.io/";
            var nameSpan = text.AsSpan(8, 8);

            var builder = new StringBuilder("Hello ");
            builder.Append(nameSpan);
            builder.AppendLine("!");

            Console.WriteLine(builder.ToString());
        }
    }
}

这个例子是从 https://walterlv.github.io/ 字符串中取出第 8 个字符开始长度为 8 的部分,随后与其它字符串进行拼接。最后,我们得到了拼接的字符串:

Hello walterlv!

这种方式取出字符串替代了 SubString 这种会额外生成临时字符串的方式。如果上述代码发生在较大或较多文本的处理中,那么反复的拼接将生成大量的临时字符串,造成大量 GC 压力;而使用 Span<T> 将不会额外生成任何临时字符串。

语言/框架的支持

然而,只有 .NET Core 2.1 是原生支持字符串的 AsSpan<T> 方法的,.NET Core 2.0、.NET Framework 4.7.2 是不支持的。.NET Core 2.0 可以无视,因为有了 2.1。但 .NET Framework 的低版本却不能无视,因为用户的计算机上通常都是安装低版本的 .NET Framework。

只有 .NET Core 2.1 支持

然而我们可以安装 System.Memory,以在低版本的 .NET 中获得字符串扩展方法 AsSpan<T> 的支持。

那么问题来了,低版本的 .NET StringBuilder 中并没有提供 Append(ReadOnlySpan<char>) 方法,于是我们即便使用高性能的方式得到了字符串的一个片段,依然无法将其反复进行拼接。

这真是一个悲伤的故事

低版本 .NET 中有限的字符串性能提升

缺少了 StringBuilderReadOnlySpan<char> 的支持,广泛使用的字符串拼接功能便没有办法获得 Span 的支持。

不过,System.Memory 中提供了其它有限的字符串处理支持,来源于以下两个类型:

前者提供从 ReadOnlySpan<char>Int32DoubleDateTimeGuid 等类型的解析,后者提供相反的转换。

期待 Microsoft 在未来版本的 System.Memory 库中提供对字符串拼接在低版本 .NET 生态中的支持。


参考资料

本文会经常更新,请阅读原文: https://walterlv.com/post/improve-string-performance-using-span.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

知识共享许可协议 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名 吕毅 (包含链接: https://walterlv.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系 (walter.lv@qq.com)