首页 > 其他分享 >使用 InterpolatedString 减少字符串拼接的 GC

使用 InterpolatedString 减少字符串拼接的 GC

时间:2023-04-12 15:46:20浏览次数:51  
标签:string InterpolatedString array 拼接 GC 字符串 日志 public

原视频链接

考虑到 Unity 准备在 2024 年前后,推出基于 dotnet Runtime 的版本,本篇文章也标记为 Unity 分类,等后面 Unity 准备好之后,再对新版的客户端进行改造

在日常开发过程中,字符串的拼接通常会占用大量的 GC,通常拼接字符串我们会使用如下几种方法

1. 1 + "/" + 2 + "/" + 3
2. StringBuilder
3. string.Format("{0}/{1}/{2}", 1, 2, 3)
4. "{1}/{2}/{3}" // 美元符号因为博客解析问题,此处省略

无论上述的哪一种方法,在 dotnet 5.0 的环境下,都无法做到 0 GC,但是在 dotnet 6.0 的版本,微软推出了一套基于 InterpolatedString 的解决方案,做到了即使是 值类型 字符串的拼接,也没有装箱/拆箱,以及 GC 问题

性能问题

很多时候开发者图方便,直接使用第一种方式,对字符串进行拼接,但实际生成的代码性能非常非常糟糕

// 原始代码
public string Concat()
{
    return 1 + "/" + 2 + "/" + 3;
}

// 实际生成的代码
public string Concat()
{
    string[] array = new string[5];
    array[0] = 1.ToString();
    array[1] = "/";
    array[2] = 2.ToString();
    array[3] = "/";
    array[4] = 3.ToString();
    return string.Concat(array);
}

同样的,如果我们使用 string.Format 函数对字符串进行拼接时,由于参数是 object,对于值类型一样会有装箱和拆箱的问题

InterpolatedString 实现

正常服务器项目的日志打印,都会定制一份自己的实现,一般用于控制日志等级,搜集日志等等。由于有这层封装,我们对于日志 GC 的整体改造会简单许多,这里以我们项目实际接入为例

注意此处需要 dotnet 6.0 以及 C#10,低版本无法使用

此时如果我们使用 $ 对字符串进行拼接时,生成的代码就完全不一样了

// 生成前
public string Dotnet5Interpolate()
{
    return "{1}/{2}/{3}"; // 省略了美元符号
}
    
// 生成后
public string Dotnet5Interpolate()
{
    return string.Format("{0}/{1}/{2}", 1, 2, 3);
}

// 生成前
public string Dotnet6Interpolate()
{
    return "{1}/{2}/{3}"; // 省略了美元符号
}

// 生成后
public string Dotnet6Interpolate()
{
    DefaultInterpolatedStringHandler defaultInterpolatedStringHandler = new DefaultInterpolatedStringHandler(2, 3);
    defaultInterpolatedStringHandler.AppendFormatted(1);
    defaultInterpolatedStringHandler.AppendLiteral("/");
    defaultInterpolatedStringHandler.AppendFormatted(2);
    defaultInterpolatedStringHandler.AppendLiteral("/");
    defaultInterpolatedStringHandler.AppendFormatted(3);
    return defaultInterpolatedStringHandler.ToStringAndClear();
}

这里的 DefaultInterpolatedStringHandler 是一个 ref struct,所以所有操作均在栈上执行,而微软为了解决 值类型 的装箱拆箱,将 AppendFormatted 方法定义为了泛型方法

public void AppendFormatted<T>(T value);

至此我们还需要对现有的日志接口进行改造,改造过程也非常简单,我们先定义 LogInterpolatedStringHandler 结构体

[InterpolatedStringHandler]
public ref struct LogInterpolatedStringHandler
{
    private DefaultInterpolatedStringHandler _inner_handler;

    public LogInterpolatedStringHandler(int literal_length, int formatted_count)
    {
        _inner_handler = new DefaultInterpolatedStringHandler(literal_length, formatted_count);
    }

    public override string ToString() => _inner_handler.ToString();

    public void AppendLiteral(string literal) => _inner_handler.AppendLiteral(literal);

    public void AppendFormatted<T>(T value) => _inner_handler.AppendFormatted(value);

    public string ToStringAndClear() => _inner_handler.ToStringAndClear();
}

假设日志有 Debug 接口,那么此处的写法如下

public static class Log
{
    public static void Debug(ref LogInterpolatedStringHandler msg)
    {
        // 注意这里记得使用 ToStringAndClear 方法
        // 底层使用了 ArrayPool,此时需要将 char[] 还给池子
        xxx.Log(msg.ToStringAndClear());
    }
}

// 后续全部省略了美元符号
// 输入我是日志,注意此处如果没有写美元符号,无法通过编译
Log.Debug("我是日志")
// 输出 我是日志 1/2/3
Log.Debug("我是日志 {1}/{2}/{3}");
int value = 123;
// 输出 123
Log.Debug("{value}");

这样我们就完成了 0GC 的字符串拼接,对于上层的业务逻辑来说,打印日志仅允许使用 $ 符号对字符串进行拼接,从而限制了一些程序喜欢使用 + 来拼接字符串的写法,而且即使是外面想使用 string.Format 传入字符串,也同样无法通过编译

标签:string,InterpolatedString,array,拼接,GC,字符串,日志,public
From: https://www.cnblogs.com/LiuOcean-Blog/p/shi-yong-interpolatedstring-jian-shao-zi-fu-chuan.

相关文章

  • 21-springcloud-feign-3-使用Feign实现消费者
    使用Feign实现消费者,我们通过下面步骤进行: 第一步:创建普通SpringBoot工程把接口放在通用的接口层、常量类、model的项目中第二步:添加依赖要添加的依赖主要是spring-cloud-starter-netflix-eureka-client和spring-cloud-starter-feign,如下:<!--spring-cloud......
  • 22-springcloud-feign-4-使用Feign实现消费者的测试
    负载均衡:我们知道,SpringCloud提供了Ribbon来实现负载均衡,使用Ribbo直接注入一个RestTemplate对象即可,RestTemplate已经做好了负载均衡的配置;在SpringCloud下,使用Feign也是直接可以实现负载均衡的,定义一个有@FeignClient注解的接口,然后使用@RequestMapping注解......
  • 17-springcloud-ribbon-3-Ribbon 负载均衡策略
    Ribbon的负载均衡策略是由IRule接口定义,该接口由如下实现:在jar包:com.netflix.ribbon#ribbon-loadbalancer中;  要使用ribbon实现负载均衡,在Spring的配置类里面把对应的负载均衡接口实现类作为一个Bean配置一下就行了;负载均衡的入口:ILoadBalancer接口如果要切换负载......
  • java 逗号拼接字符串
    逗号拼接字符串可以使用String类的静态方法join()来实现这个功能,示例代码如下:```javapublicclassPhoneNumbers{publicstaticvoidmain(String[]args){StringphoneNumber1="18801083588";StringphoneNumber2="15709106355";Stri......
  • 16-springcloud-ribbon-2-ribbon实现服务调用
    1、首先加入ribbon的依赖,但是eureka已经依赖了ribbon,所以这里不需要再引用ribbon的依赖;2、要使用ribbon,只需要一个注解: @Bean@LoadBalancedpublic RestTemplaterestTemplate(){    RestTemplaterestTemplate=new RestTemplate();    return restTemplate;}在R......
  • springcloud gateway根据服务名称进行路由失败There was an unexpected error (type=S
    出现错误,如下图:解决办法:检查自己的yaml文件:server:port:88spring:application:name:applicationNamecloud:nacos:discovery:server-addr:127.0.0.1:8848gateway:#开启服务发现路由(不开启跨域问题可能无法解决)disco......
  • 玖章算术CEO叶正盛在数据技术嘉年华分享NineData AIGC的应用实践
    4月8日下午,为期两天的第十二届数据技术嘉年华(DTC2023)在北京新云南皇冠假日酒店圆满落下帷幕。大会得到了工业和信息化部电子五所的支持和指导,围绕“开源·融合·数字化——引领数据技术发展,释放数据要素价值”这一主题,通过一场主论坛和十二场专题论坛,汇聚“产学研”各界数据技术......
  • 【AGC】崩溃服务数据上报常见的几个问题
    最近开发者使用崩溃服务遇到的一些数据异常问题,我在这里汇总一下,以后遇到相似的问题可以以此为参考。 【问题描述1】iOS崩溃数据“按用户搜索”页,“过去7天”是有数据的,但“统计”页没有。​​【解决方案】查询了后台上报日志,发现没有上报应用的启动事件,只上报了$HA_ERRO......
  • AIGC教程:如何使用Stable Diffusion生成风格化游戏物品和图标
    GameLook报道/随着生成型AI的能力提升,越来越多的开发者开始尝试用StableDiffusion提升自己的研发效率。在RPG游戏的制作当中,数量庞大的游戏内物品是非常耗时且费力的部分,装备、道具、药剂等物品可能数以千计,从概念设计到最终放到游戏里的资源,可能耗费很长时间和......
  • gcc降级
    $sudoaptremovegcc$sudoapt-getinstallgcc-7g++-7-y$sudoln-s/usr/bin/gcc-7/usr/bin/gcc$sudoln-s/usr/bin/g++-7/usr/bin/g++$sudoln-s/usr/bin/gcc-7/usr/bin/cc$sudoln-s/usr/bin/g++-7/usr/bin/c++$gcc--version如果 sudoaptrem......