广告
返回顶部
首页 > 资讯 > 精选 >.NET性能优化ValueStringBuilder拼接字符串如何使用
  • 104
分享到

.NET性能优化ValueStringBuilder拼接字符串如何使用

2023-07-02 08:07:45 104人浏览 泡泡鱼
摘要

今天小编给大家分享一下.net性能优化ValueStringBuilder拼接字符串如何使用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起

今天小编给大家分享一下.net性能优化ValueStringBuilder拼接字符串如何使用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。

ValueStringBuilder

ValueStringBuilder不是一个公开的api,但是它被大量用于.NET的基础类库中,由于它是值类型的,所以它本身不会在堆上分配,不会有GC的压力。
微软提供的ValueStringBuilder有两种使用方式,一种是自己已经有了一块内存空间可供字符串构建使用。这意味着你可以使用栈空间,也可以使用堆空间甚至非托管堆的空间,这对于GC来说是非常友好的,在高并发情况下能大大降低GC压力。

// 构造函数:传入一个Span的Buffer数组public ValueStringBuilder(Span<char> initialBuffer);// 使用方式:// 栈空间var vsb = new ValueStringBuilder(stackalloc char[512]);// 普通数租var vsb = new ValueStringBuilder(new char[512]);// 使用非托管堆var length = 512;var ptr = NativeMemory.Alloc((nuint)(512 * Unsafe.SizeOf<char>()));var span = new Span<char>(ptr, length);var vsb = new ValueStringBuilder(span);.....NativeMemory.Free(ptr); // 非托管堆用完一定要Free

另外一种方式是指定一个容量,它会从默认的ArrayPoolchar对象池中获取缓冲空间,因为使用的是对象池,所以对于GC来说也是比较友好的,千万需要注意,池中的对象一定要记得归还。

// 传入预计的容量public ValueStringBuilder(int initialCapacity)  {      // 从对象池中获取缓冲区    _arrayToReturnToPool = ArrayPool<char>.Shared.Rent(initialCapacity);      ......}

那么我们就来比较一下使用+=StringBuilderValueStringBuilder这几种方式的性能吧。

// 一个简单的类public class SomeClass  {      public int Value1; public int Value2; public float Value3;      public double Value4; public string? Value5; public decimal Value6;      public DateTime Value7; public TimeOnly Value8; public DateOnly Value9;      public int[]? Value10;  }// Benchmark类[MemoryDiagnoser]  [htmlExporter]  [Orderer(SummaryOrderPolicy.FastestToSlowest)]  public class StringBuilderBenchmark  {      private static readonly SomeClass Data;      static StringBuilderBenchmark()      {          var baseTime = DateTime.Now;          Data = new SomeClass          {              Value1 = 100, Value2 = 200, Value3 = 333,              Value4 = 400, Value5 = string.Join('-', Enumerable.Range(0, 10000).Select(i => i.ToString())),              Value6 = 655, Value7 = baseTime.AddHours(12),              Value8 = TimeOnly.MinValue, Value9 = DateOnly.MaxValue,              Value10 = Enumerable.Range(0, 5).ToArray()          };      }    // 使用我们熟悉的StringBuilder    [Benchmark(Baseline = true)]      public string StringBuilder()      {          var data = Data;          var sb = new StringBuilder();          sb.Append("Value1:"); sb.Append(data.Value1);          if (data.Value2 > 10)          {              sb.Append(" ,Value2:"); sb.Append(data.Value2);          }          sb.Append(" ,Value3:"); sb.Append(data.Value3);          sb.Append(" ,Value4:"); sb.Append(data.Value4);          sb.Append(" ,Value5:"); sb.Append(data.Value5);          if (data.Value6 > 20)          {              sb.Append(" ,Value6:"); sb.AppendFORMat("{0:F2}", data.Value6);          }          sb.Append(" ,Value7:"); sb.AppendFormat("{0:yyyy-MM-dd HH:mm:ss}", data.Value7);          sb.Append(" ,Value8:"); sb.AppendFormat("{0:HH:mm:ss}", data.Value8);          sb.Append(" ,Value9:"); sb.AppendFormat("{0:yyyy-MM-dd}", data.Value9);          sb.Append(" ,Value10:");          if (data.Value10 is null or {Length: 0}) return sb.ToString();          for (int i = 0; i < data.Value10.Length; i++)          {              sb.Append(data.Value10[i]);          }          return sb.ToString();      }    // StringBuilder使用Capacity    [Benchmark]      public string StringBuilderCapacity()      {          var data = Data;          var sb = new StringBuilder(20480);          sb.Append("Value1:"); sb.Append(data.Value1);          if (data.Value2 > 10)          {              sb.Append(" ,Value2:"); sb.Append(data.Value2);          }          sb.Append(" ,Value3:"); sb.Append(data.Value3);          sb.Append(" ,Value4:"); sb.Append(data.Value4);          sb.Append(" ,Value5:"); sb.Append(data.Value5);          if (data.Value6 > 20)          {              sb.Append(" ,Value6:"); sb.AppendFormat("{0:F2}", data.Value6);          }          sb.Append(" ,Value7:"); sb.AppendFormat("{0:yyyy-MM-dd HH:mm:ss}", data.Value7);          sb.Append(" ,Value8:"); sb.AppendFormat("{0:HH:mm:ss}", data.Value8);          sb.Append(" ,Value9:"); sb.AppendFormat("{0:yyyy-MM-dd}", data.Value9);          sb.Append(" ,Value10:");          if (data.Value10 is null or {Length: 0}) return sb.ToString();          for (int i = 0; i < data.Value10.Length; i++)          {              sb.Append(data.Value10[i]);          }          return sb.ToString();      }      // 直接使用+=拼接字符串    [Benchmark]      public string StringConcat()      {          var str = "";          var data = Data;          str += ("Value1:"); str += (data.Value1);          if (data.Value2 > 10)          {              str += " ,Value2:"; str += data.Value2;          }          str += " ,Value3:"; str += (data.Value3);          str += " ,Value4:"; str += (data.Value4);          str += " ,Value5:"; str += (data.Value5);          if (data.Value6 > 20)          {              str += " ,Value6:"; str += data.Value6.ToString("F2");          }          str += " ,Value7:"; str += data.Value7.ToString("yyyy-MM-dd HH:mm:ss");          str += " ,Value8:"; str += data.Value8.ToString("HH:mm:ss");          str += " ,Value9:"; str += data.Value9.ToString("yyyy-MM-dd");          str += " ,Value10:";          if (data.Value10 is not null && data.Value10.Length > 0)          {              for (int i = 0; i < data.Value10.Length; i++)              {                  str += (data.Value10[i]);              }             }          return str;      }      // 使用栈上分配的ValueStringBuilder    [Benchmark]      public string ValueStringBuilderOnStack()      {          var data = Data;          Span<char> buffer = stackalloc char[20480];          var sb = new ValueStringBuilder(buffer);          sb.Append("Value1:"); sb.AppendSpanFormattable(data.Value1);          if (data.Value2 > 10)          {              sb.Append(" ,Value2:"); sb.AppendSpanFormattable(data.Value2);          }          sb.Append(" ,Value3:"); sb.AppendSpanFormattable(data.Value3);          sb.Append(" ,Value4:"); sb.AppendSpanFormattable(data.Value4);          sb.Append(" ,Value5:"); sb.Append(data.Value5);          if (data.Value6 > 20)          {              sb.Append(" ,Value6:"); sb.AppendSpanFormattable(data.Value6, "F2");          }          sb.Append(" ,Value7:"); sb.AppendSpanFormattable(data.Value7, "yyyy-MM-dd HH:mm:ss");          sb.Append(" ,Value8:"); sb.AppendSpanFormattable(data.Value8, "HH:mm:ss");          sb.Append(" ,Value9:"); sb.AppendSpanFormattable(data.Value9, "yyyy-MM-dd");          sb.Append(" ,Value10:");          if (data.Value10 is not null && data.Value10.Length > 0)          {              for (int i = 0; i < data.Value10.Length; i++)              {                  sb.AppendSpanFormattable(data.Value10[i]);              }             }          return sb.ToString();      }    // 使用ArrayPool 堆上分配的StringBuilder    [Benchmark]      public string ValueStringBuilderOnHeap()      {          var data = Data;          var sb = new ValueStringBuilder(20480);          sb.Append("Value1:"); sb.AppendSpanFormattable(data.Value1);          if (data.Value2 > 10)          {              sb.Append(" ,Value2:"); sb.AppendSpanFormattable(data.Value2);          }          sb.Append(" ,Value3:"); sb.AppendSpanFormattable(data.Value3);          sb.Append(" ,Value4:"); sb.AppendSpanFormattable(data.Value4);          sb.Append(" ,Value5:"); sb.Append(data.Value5);          if (data.Value6 > 20)          {              sb.Append(" ,Value6:"); sb.AppendSpanFormattable(data.Value6, "F2");          }          sb.Append(" ,Value7:"); sb.AppendSpanFormattable(data.Value7, "yyyy-MM-dd HH:mm:ss");          sb.Append(" ,Value8:"); sb.AppendSpanFormattable(data.Value8, "HH:mm:ss");          sb.Append(" ,Value9:"); sb.AppendSpanFormattable(data.Value9, "yyyy-MM-dd");          sb.Append(" ,Value10:");          if (data.Value10 is not null && data.Value10.Length > 0)          {              for (int i = 0; i < data.Value10.Length; i++)              {                  sb.AppendSpanFormattable(data.Value10[i]);              }             }        return sb.ToString();      }}

结果如下所示。

.NET性能优化ValueStringBuilder拼接字符串如何使用

从上图的结果中,我们可以得出如下的结论。

  • 使用StringConcat是最慢的,这种方式是无论如何都不推荐的。

  • 使用StringBuilder要比使用StringConcat快6.5倍,这是推荐的方法。

  • 设置了初始容量的StringBuilder要比直接使用StringBuilder快25%,正如我在你应该为集合类型设置初始大小一样,设置初始大小绝对是相当推荐的做法。

  • 栈上分配的ValueStringBuilderStringBuilder要快50%,比设置了初始容量的StringBuilder还快25%,另外它的GC次数是最低的。

  • 堆上分配的ValueStringBuilderStringBuilder要快55%,他的GC次数稍高与栈上分配。
    从上面的结论中,我们可以发现ValueStringBuilder的性能非常好,就算是在栈上分配缓冲区,性能也比StringBuilder快25%。

源码解析

ValueStringBuilder源码不长,我们挑几个重要的方法给大家分享一下,部分源码如下。

// 使用 ref struct 该对象只能在栈上分配public ref struct ValueStringBuilder{    // 如果从ArrayPool里分配buffer 那么需要存储一下    // 以便在Dispose时归还    private char[]? _arrayToReturnToPool;    // 暂存外部传入的buffer    private Span<char> _chars;    // 当前字符串长度    private int _pos;    // 外部传入buffer    public ValueStringBuilder(Span<char> initialBuffer)    {        // 使用外部传入的buffer就不使用从pool里面读取的了        _arrayToReturnToPool = null;        _chars = initialBuffer;        _pos = 0;    }    public ValueStringBuilder(int initialCapacity)    {        // 如果外部传入了capacity 那么从ArrayPool里面获取        _arrayToReturnToPool = ArrayPool<char>.Shared.Rent(initialCapacity);        _chars = _arrayToReturnToPool;        _pos = 0;    }    // 返回字符串的Length 由于Length可读可写    // 所以重复使用ValueStringBuilder只需将Length设置为0    public int Length    {        get => _pos;        set        {            Debug.Assert(value >= 0);            Debug.Assert(value <= _chars.Length);            _pos = value;        }    }    ......    [MethodImpl(MethodImplOptions.AggressiveInlining)]    public void Append(char c)    {        // 添加字符非常高效 直接设置到对应Span位置即可        int pos = _pos;        if ((uint) pos < (uint) _chars.Length)        {            _chars[pos] = c;            _pos = pos + 1;        }        else        {            // 如果buffer空间不足,那么会走            GrowAndAppend(c);        }    }    [MethodImpl(MethodImplOptions.AggressiveInlining)]    public void Append(string? s)    {        if (s == null)        {            return;        }        // 追加字符串也是一样的高效        int pos = _pos;        // 如果字符串长度为1 那么可以直接像追加字符一样        if (s.Length == 1 && (uint) pos < (uint) _chars .Length)        {            _chars[pos] = s[0];            _pos = pos + 1;        }        else        {            // 如果是多个字符 那么使用较慢的方法            AppendSlow(s);        }    }    private void AppendSlow(string s)    {        // 追加字符串 空间不够先扩容        // 然后使用Span复制 相当高效        int pos = _pos;        if (pos > _chars.Length - s.Length)        {            Grow(s.Length);        }        s#if !netcoreAPP                .AsSpan()#endif            .CopyTo(_chars.Slice(pos));        _pos += s.Length;    }    // 对于需要格式化的对象特殊处理    [MethodImpl(MethodImplOptions.AggressiveInlining)]    public void AppendSpanFormattable<T>(T value, string? format = null, IFormatProvider? provider = null)        where T : ISpanFormattable    {        // ISpanFormattable非常高效        if (value.TryFormat(_chars.Slice(_pos), out int charsWritten, format, provider))        {            _pos += charsWritten;        }        else        {            Append(value.ToString(format, provider));        }    }    [MethodImpl(MethodImplOptions.NoInlining)]    private void GrowAndAppend(char c)    {        // 单个字符扩容在添加        Grow(1);        Append(c);    }    // 扩容方法    [MethodImpl(MethodImplOptions.NoInlining)]    private void Grow(int additionalCapacityBeyondPos)    {        Debug.Assert(additionalCapacityBeyondPos > 0);        Debug.Assert(_pos > _chars.Length - additionalCapacityBeyondPos,            "Grow called incorrectly, no resize is needed.");        // 同样也是2倍扩容,默认从对象池中获取buffer        char[] poolArray = ArrayPool<char>.Shared.Rent((int) Math.Max((uint) (_pos + additionalCapacityBeyondPos),            (uint) _chars.Length * 2));        _chars.Slice(0, _pos).CopyTo(poolArray);        char[]? toReturn = _arrayToReturnToPool;        _chars = _arrayToReturnToPool = poolArray;        if (toReturn != null)        {            // 如果原本就是使用的对象池 那么必须归还            ArrayPool<char>.Shared.Return(toReturn);        }    }    //     [MethodImpl(MethodImplOptions.AggressiveInlining)]    public void Dispose()    {        char[]? toReturn = _arrayToReturnToPool;        this = default; // 为了安全,在释放时置空当前对象        if (toReturn != null)        {            // 一定要记得归还对象池            ArrayPool<char>.Shared.Return(toReturn);        }    }}

从上面的源码我们可以总结ValueStringBuilder的几个特征:

  • 比起StringBuilder来说,实现方式非常简单。

  • 一切都是为了高性能,比如各种Span的用法,各种内联参数,以及使用对象池等等。

  • 内存占用非常低,它本身就是结构体类型,另外它是ref struct,意味着不会被装箱,不会在堆上分配。

适用场景

ValueStringBuilder是一种高性能的字符串创建方式,针对于不同的场景,可以有不同的使用方式。

非常高频次的字符串拼接的场景,并且字符串长度较小,此时可以使用栈上分配的ValueStringBuilder

大家都知道现在ASP.net core性能非常好,在其依赖的内部库UrlBuilder中,就使用栈上分配,因为栈上分配在当前方法结束后内存就会回收,所以不会造成任何GC压力。

.NET性能优化ValueStringBuilder拼接字符串如何使用

非常高频次的字符串拼接场景,但是字符串长度不可控,此时使用ArrayPool指定容量的ValueStringBuilder。比如在.NET BCL库中有很多场景使用,比如动态方法的ToString实现。从池中分配虽然没有栈上分配那么高效,但是一样的能降低内存占用和GC压力。

.NET性能优化ValueStringBuilder拼接字符串如何使用

非常高频次的字符串拼接场景,但是字符串长度可控,此时可以栈上分配和ArrayPool分配联合使用,比如正则表达式解析类中,如果字符串长度较小那么使用栈空间,较大那么使用ArrayPool。

.NET性能优化ValueStringBuilder拼接字符串如何使用

需要注意的场景

async\await中无法使用ValueStringBuilder。原因大家也都知道,因为ValueStringBuilderref struct,它只能在栈上分配,async\await会编译成状态机拆分await前后的方法,所以ValueStringBuilder不好在方法内传递,不过编译器也会警告。

.NET性能优化ValueStringBuilder拼接字符串如何使用

无法将ValueStringBuilder作为返回值返回,因为在当前栈上分配,方法结束后它会被释放,返回它将指向未知的地址。这个编译器也会警告。

.NET性能优化ValueStringBuilder拼接字符串如何使用

如果要将ValueStringBuilder传递给其它方法,那么必须使用ref传递,否则发生值拷贝会存在多个实例。这个编译器不会警告,但是你必须非常注意。

.NET性能优化ValueStringBuilder拼接字符串如何使用

 如果使用栈上分配,那么Buffer大小控制在5KB内比较稳妥。

以上就是“.NET性能优化ValueStringBuilder拼接字符串如何使用”这篇文章的所有内容,感谢各位的阅读!相信大家阅读完这篇文章都有很大的收获,小编每天都会为大家更新不同的知识,如果还想学习更多的知识,请关注编程网精选频道。

--结束END--

本文标题: .NET性能优化ValueStringBuilder拼接字符串如何使用

本文链接: https://www.lsjlt.com/news/340740.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • .NET性能优化ValueStringBuilder拼接字符串如何使用
    今天小编给大家分享一下.NET性能优化ValueStringBuilder拼接字符串如何使用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起...
    99+
    2023-07-02
  • .NET性能优化ValueStringBuilder拼接字符串使用实例
    目录前言ValueStringBuilder源码解析适用场景需要注意的场景总结前言 这一次要和大家分享的一个Tips是在字符串拼接场景使用的,我们经常会遇到有很多短小的字符串需要拼接...
    99+
    2022-11-13
  • GoLang中拼接字符串性能优化方法详解
    字符串在内存中是不可变的,放在只读内存段,因此你可以使用str[0]来访问,但是不能使用str[0]='a'来修改。 修改字符串实际上是重新放入新的地址,因此拼接字符...
    99+
    2023-02-03
    GoLang拼接字符串 GoLang拼接字符串性能优化
  • Golong字符串拼接性能优化及原理介绍
    目录1.字符串高效拼接1.1 常见的字符串拼接1.2 字符串拼接测试1.3 推荐2.相关原理2.1 + 号2.2 strings.Builder 与 bytes.Buffer2.2....
    99+
    2023-05-14
    Go字符串拼接 Go字符串拼接原理 Go字符串拼接性能
  • Golong字符串拼接性能优化方法及原理是什么
    这篇文章主要介绍了Golong字符串拼接性能优化方法及原理是什么的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Golong字符串拼接性能优化方法及原理是什么文章都会有所收获,下面我们一起来看看吧。1.字符串高效...
    99+
    2023-07-05
  • web中如何使用二分法拼接字符串
    这篇文章将为大家详细讲解有关web中如何使用二分法拼接字符串,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。字符串拼接通常用循环,这样如果循环次数过大,就会影响性能,使用一...
    99+
    2022-10-19
  • Mysql性能优化:如何给字符串加索引?
    导读 现代大部分的登录系统都支持邮箱、手机号码登录两种方式,那么如何在邮箱或者手机号码这个字符串上建立索引才能保证性能最佳呢? 今天这篇文章就来探讨一下在Mysql中如何给一个字符串加索引才能达到性能最佳。 本文首发于作...
    99+
    2018-08-13
    Mysql性能优化:如何给字符串加索引?
  • 如何使用PHP操作符将两个字符串拼接在一起
    这篇文章主要介绍“如何使用PHP操作符将两个字符串拼接在一起”,在日常操作中,相信很多人在如何使用PHP操作符将两个字符串拼接在一起问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”如何使用PHP操作符将两个字符...
    99+
    2023-06-20
  • Java 性能优化中如何进行字符串过滤
    本篇文章给大家分享的是有关Java 性能优化中如何进行字符串过滤,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。★一个简单的需求  首先描述一下需求:给定一个 String 对象...
    99+
    2023-06-02
  • 如何在Python 中使用 join() 函数把列表拼接成一个字符串
    目录1.设置 end=’’2.拼接字符串(string)3.举例内容概要:如何把列表中的元素拼接为一个字符串呢?本文介绍了采用 join() 函数的解决方法。...
    99+
    2022-11-13
  • 如何在java项目中使用字符串与数字的性能
    这期内容当中小编将会给大家带来有关如何在java项目中使用字符串与数字的性能,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。对于大数据处理来说,从字符串替换为数字类型,可以极大地节省内存、磁盘存储以及网络带...
    99+
    2023-05-31
    java 字符串 ava
  • 如何使用ASP存储接口shell优化网站性能?
    ASP是一种非常流行的网页开发技术,它是一种基于服务器端的脚本语言,可以用来开发动态网站。但是,在ASP网站开发中,很容易遇到网站性能问题。在本文中,我们将介绍如何使用ASP存储接口shell优化网站性能。 一、什么是ASP存储接口shel...
    99+
    2023-11-06
    存储 接口 shell
  • 如何使用ASP编程算法优化HTTP接口的性能?
    ASP是一种流行的Web应用程序框架,它使用VBScript或JScript编写。ASP编程算法可以优化HTTP接口的性能,使得网站能够更快地响应用户请求。在本篇文章中,我们将介绍如何使用ASP编程算法来优化HTTP接口的性能。 第一步是使...
    99+
    2023-11-09
    编程算法 http 接口
  • Python并发编程中,如何使用接口优化程序性能?
    在Python编程中,我们经常会遇到需要同时处理多个任务的情况。这时,我们就需要使用并发编程来提高程序的性能。而在Python的并发编程中,接口是一个非常重要的概念。本文将介绍Python中的接口是什么,以及如何使用接口优化程序性能。 一...
    99+
    2023-05-26
  • PHP自然语言处理:如何使用存储关键字优化性能?
    自然语言处理(NLP)是一种人工智能技术,旨在使计算机能够理解和处理自然语言。PHP作为一种流行的服务器端脚本语言,也可以使用各种NLP技术。 在NLP中,一个常见的任务是从文本中提取关键字。这些关键字可以用于搜索引擎、文本分类、情感分析...
    99+
    2023-09-01
    存储 关键字 自然语言处理
  • 如何在GO语言中使用重定向技术来优化接口性能?
    随着互联网的快速发展,Web应用程序的性能和可伸缩性变得越来越重要。在Web应用程序中,接口是非常重要的组成部分。接口的性能往往影响整个应用程序的性能。在本文中,我将介绍如何使用重定向技术来优化接口性能,特别是在GO语言中。 一、什么是重定...
    99+
    2023-06-30
    重定向 接口 ide
  • 如何优化Laravel接口性能?使用PHP缓存是不是一个好选择?
    Laravel 是一个非常流行的 PHP 框架,为开发人员提供了一个快速构建应用程序的平台。由于它的易用性和灵活性,Laravel 已经成为众多开发人员的首选框架。然而,在开发 Laravel 应用程序时,性能问题往往是一个需要关注的问题。...
    99+
    2023-09-27
    缓存 laravel 接口
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作