字符串太占内存了,我想了各种奇思淫巧对它进行压缩

一:背景

1. 讲故事

在我们的一个全内存项目中,需要将一家大品牌店铺小千万的trade灌入到内存中,大家知道trade中一般会有订单来源,省市区 ,当把这些字段灌进去后,你会发现他们特别侵蚀内存,因为都是字符串类型,不知道大家对内存侵蚀性是不是很清楚,我就问一个问题。

Answer: 一个空字符串占用多大内存?你知道吗?

思考之后,下面我们就一起验证下,使用windbg去托管堆一查究竟,代码如下:

static void Main(string[] args){string s = string.Empty;Console.ReadLine();}0:000> !clrstack -l
OS Thread Id: 0x308c (0)Child SP               IP Call Site
ConsoleApp6.Program.Main(System.String[]) [C:\dream\Csharp\ConsoleApp1\ConsoleApp6\Program.cs @ 19]LOCALS:0x00000087391febd8 = 0x000002605da91420
0:000> !DumpObj /d 000002605da91420
Name:        System.String
String:
Fields:MT    Field   Offset                 Type VT     Attr            Value Name
00007ff9eb2b85a0  4000281        8         System.Int32  1 instance                0 m_stringLength
00007ff9eb2b6838  4000282        c          System.Char  1 instance                0 m_firstChar
00007ff9eb2b59c0  4000286       d8        System.String  0   shared           static Empty>> Domain:Value  000002605beb2230:NotInit  <<
0:000> !objsize 000002605da91420
sizeof(000002605da91420) = 32 (0x20) bytes (System.String)

从图中你可以看到,仅仅一个空字符串就要占用 32byte,如果500w个空字符串就是: 32byte x 500w = 152M,是不是不算不知道,一算吓一跳。。。这还仅仅是一个什么都没有的空字符串哦。

2. 回归到Trade

问题也已经摆出来了,接下来回归到Trade中,为了方便演示,先模拟以文件的形式从数据库读取20w的trade。

    class Program{static void Main(string[] args){var trades = Enumerable.Range(0, 20 * 10000).Select(m => new Trade(){TradeID = m,TradeFrom = File.ReadLines(Environment.CurrentDirectory + "//orderfrom.txt").ElementAt(m % 4)}).ToList();GC.Collect();  //方便测试,把临时变量清掉Console.WriteLine("执行成功");Console.ReadLine();}}class Trade{public int TradeID { get; set; }public string TradeFrom { get; set; }}

然后用windbg去跑一下托管堆,再量一下trades的大小。


0:000> !dumpheap -stat
Statistics:MT    Count    TotalSize Class Name
00007ff9eb2b59c0   200200      7010246 System.String0:000> !objsize 0x000001a5860629a8
sizeof(000001a5860629a8) = 16097216 (0xf59fc0) bytes (System.Collections.Generic.List`1[[ConsoleApp6.Trade, ConsoleApp6]])

从上面输出中可以看到托管堆有200200 = 20w(程序分配)+ 200(系统分配)个,然后再看size: 16097216/1024/1024= 15.35M,这就是展示的所有原始情况。

二:压缩技巧分析

1. 使用字典化处理

其实在托管堆上有20w个字符串,但你仔细观察一下会发现其实就是4种状态的重复显示,要么一淘,要么淘宝。。。这就给了我优化机会,何不在获取数据的时候构建好OrderFrom的字典,然后在trade中附增一个TradeFromID记录字典中的映射值,因为特征值少,用byte就可以了,有了这个思想,可以把代码修改如下:

class Program{public static Dictionary<int, string> orderfromDict = new Dictionary<int, string>();static void Main(string[] args){var trades = Enumerable.Range(0, 20 * 10000).Select(m =>{var tradefrom = File.ReadLines(Environment.CurrentDirectory + "//orderfrom.txt").ElementAt(m % 4);var kv = orderfromDict.FirstOrDefault(k => k.Value == tradefrom);if (kv.Key == 0){orderfromDict.Add(orderfromDict.Count + 1, tradefrom);}var trade = new Trade() { TradeID = m, TradeFromID = (byte)kv.Key };return trade;}).ToList();GC.Collect();  //方便测试,把临时变量清掉Console.WriteLine("执行成功");Console.ReadLine();}}class Trade{public int TradeID { get; set; }public byte TradeFromID { get; set; }public string TradeFrom{get{return Program.orderfromDict[TradeFromID];}}}

代码还是很简单的,接下来用windbg看一下空间到底压缩了多少?

0:000> !dumpheap -stat
Statistics:MT    Count    TotalSize Class Name
00007ff9eb2b59c0      204        10386 System.String0:000> !clrstack -l
OS Thread Id: 0x2ce4 (0)Child SP               IP Call Site
ConsoleApp6.Program.Main(System.String[]) [C:\dream\Csharp\ConsoleApp1\ConsoleApp6\Program.cs @ 42]LOCALS:0x0000006f4d9ff078 = 0x0000016fdcf82ab80000006f4d9ff288 00007ff9ecd96c93 [GCFrame: 0000006f4d9ff288]
0:000> !objsize 0x0000016fdcf82ab8
sizeof(0000016fdcf82ab8) = 6897216 (0x693e40) bytes (System.Collections.Generic.List`1[[ConsoleApp6.Trade, ConsoleApp6]])

从上面的输出中可以看到,托管堆上string现在是:204 = 4(程序分配) + 200(系统分配)个,这4个就是字典中的4个哦,空间的话:6897216 /1024/1024= 6.57M,对应之前的 15.35M优化了将近60%。

虽然优化了60%,但这种优化是破坏性的优化,需要修改我的Trade结构,同时还要定义个Dictionary,而且还有不小幅度的修改业务逻辑,大家都知道线上的代码是能不改则不改,不改肯定没错,改出问题肯定是你兜着走,是吧,那问题就来了,如何最小化的修改而且还能压缩空间,有这样两全其美的事情吗???

2. 利用字符串驻留池

貌似一说出来,大家都如梦初醒,驻留池的出现就是为了解决这个问题,CLR会在内部维护了一个我刚才定义的字典机制,重复的字符串就不需要在堆上再次分配,直接存它的引用地址即可,如果你不清楚驻留池,建议看一下我这篇:https://www.cnblogs.com/huangxincheng/p/12799736.html

接下来只需要在tradefrom 字段包一层 string.Intern 即可,改动不要太小,代码如下:

static void Main(string[] args){var trades = Enumerable.Range(0, 20 * 10000).Select(m => new Trade(){TradeID = m,TradeFrom = string.Intern(File.ReadLines(Environment.CurrentDirectory + "//orderfrom.txt").ElementAt(m % 4)),   //包一层 string.Intern}).ToList();GC.Collect();  //方便测试,把临时变量清掉Console.WriteLine("执行成功");Console.ReadLine();}

然后用windbg抓一下托管堆。


0:000> !dumpheap -stat
Statistics:MT    Count    TotalSize Class Name
00007ff9eb2b59c0      204        10386 System.String0:000> !clrstack -l
OS Thread Id: 0x13f0 (0)Child SP               IP Call SiteConsoleApp6.Program.Main(System.String[]) [C:\dream\Csharp\ConsoleApp1\ConsoleApp6\Program.cs @ 27]LOCALS:0x0000005e4d3ff0a8 = 0x000001f8a15129a80000005e4d3ff2b8 00007ff9ecd96c93 [GCFrame: 0000005e4d3ff2b8]
0:000> !objsize 0x000001f8a15129a8
sizeof(000001f8a15129a8) = 8497368 (0x81a8d8) bytes (System.Collections.Generic.List`1[[ConsoleApp6.Trade, ConsoleApp6]])

观察后发现,当用了驻留池之后空间为: 8497368 /1024/1024 =8.1M,你可能有疑问,为什么和字典化相比内存要大24%呢?仔细观察你会发现,当用驻留池后,List<Trade> 中的TradeFrom存的是string在堆中的内存地址,在x64机器上要占用8个字节,而字典化方式内存堆上Trade是不分配TradeFrom,而是用了一个byte来替代,总体来说相当于一个trade省了7byte的空间,然后用windbg看一下。


0:000> !da -length 1 -details 000001f8b16f9b68
Name:        ConsoleApp6.Trade[]
Size:        2097176(0x200018) bytes
Array:       Rank 1, Number of elements 262144, Type CLASSFields:MT    Field   Offset                 Type VT     Attr            Value Name00007ff9eb2b85a0  4000001       10             System.Int32      1     instance                    0     <TradeID>k__BackingField00007ff9eb2b59c0  4000002        8            System.String      0     instance     000001f8a1516030     <TradeFrom>k__BackingField0:000> !DumpObj /d 000001f8a1516030
Name:        System.String
String:      WAP

可以看到, 000001f8a1516030 就是 堆上 string=Wap的引用地址,这个地址占用了8byte空间。

再回头dump一下使用字典化方式的Trade,可以看到它是没有 <TradeFrom>k__BackingField 字段的。


0:000> !da -length 1 -details 000001ed52759ac0
Name:        ConsoleApp6.Trade[]
Size:        262168(0x40018) bytes
Array:       Rank 1, Number of elements 32768, Type CLASSFields:MT    Field   Offset                 Type VT     Attr            Value Name00007ff9eb2b85a0  4000002        8             System.Int32      1     instance                    0     <TradeID>k__BackingField00007ff9eb2b7d20  4000003        c              System.Byte      1     instance                    0     <TradeFromID>k__BackingField

三:总结

大家可以根据自己的情况使用,使用驻留池方式是改变最小的,简单粗暴,自己构建字典化虽然最省内存,但需要修正业务逻辑,这个风险自担哦。。。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/309443.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[JavaWeb-HTML]HTML标签_表格标签

表格标签&#xff1a; * table&#xff1a;定义表格* width&#xff1a;宽度* border&#xff1a;边框* cellpadding&#xff1a;定义内容和单元格的距离* cellspacing&#xff1a;定义单元格之间的距离。如果指定为0&#xff0c;则单元格的线会合为一条、* bgcolor&#xff1a…

玩转二叉树 (25 分) 知中序遍历和前序遍历,求做个镜面反转后的层序遍历

题目&#xff1a; 给定一棵二叉树的中序遍历和前序遍历&#xff0c;请你先将树做个镜面反转&#xff0c;再输出反转后的层序遍历的序列。所谓镜面反转&#xff0c;是指将所有非叶结点的左右孩子对换。这里假设键值都是互不相等的正整数。 输入格式&#xff1a; 输入第一行给…

[JavaWeb-HTML]HTML标签_语义化标签

语义化标签&#xff1a; 语义化标签:html5中为了提高程序的可读性&#xff0c;提供了一些标签。1. <header>&#xff1a;页眉2. <footer>&#xff1a;页脚

使用dotnet Cli向nuget发布包

长话短说&#xff0c; 今天分享如何在nuget.org创建并发布.NET Standard package。前置安装勾选.NET Core开发套件的Visual Studio; 安装dotnet Cli从VS2017开始&#xff0c;dotnet Cli已经自动在.NET开发套件中被安装&#xff1b;使用SDK-style format&#xff08;SDK属性&…

STL中vector建立最大堆和最小堆

1.堆的概念&#xff1a; 堆是一种非线性结构&#xff0c;可以把堆看作一个数组&#xff0c;也可以被看作一个完全二叉树&#xff0c;通俗来讲堆其实就是利用完全二叉树的结构来维护的一维数组按照堆的特点可以把堆分为大顶堆和小顶堆 大顶堆&#xff1a;每个结点的值都大于或…

结合 AOP 轻松处理事件发布处理日志

结合 AOP 轻松处理事件发布处理日志Intro前段时间&#xff0c;实现了 EventBus 以及 EventQueue 基于 Event 的事件处理&#xff0c;但是没有做日志&#xff08;EventLog&#xff09;相关的部分&#xff0c;原本想增加两个接口&#xff0c; 处理事件发布日志和事件处理日志&…

[JavaWeb-HTML]HTML标签_块标签

div和span&#xff1a; * div:每一个div占满一整行。块级标签* span&#xff1a;文本信息在一行展示&#xff0c;行内标签 内联标签

关于堆的判断 (25 分)

题目&#xff1a; 将一系列给定数字顺序插入一个初始为空的小顶堆H[]。随后判断一系列相关命题是否为真。命题分下列几种&#xff1a; x is the root&#xff1a;x是根结点&#xff1b; x and y are siblings&#xff1a;x和y是兄弟结点&#xff1b; x is the parent of y&am…

基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(三)

系列文章使用 abp cli 搭建项目给项目瘦身&#xff0c;让它跑起来完善与美化&#xff0c;Swagger登场数据访问和代码优先自定义仓储之增删改查统一规范API&#xff0c;包装返回模型再说Swagger&#xff0c;分组、描述、小绿锁接入GitHub&#xff0c;用JWT保护你的API异常处理和…

[JavaWeb-HTML]HTML标签_链接标签

链接标签&#xff1a; * a:定义一个超链接* 属性&#xff1a;* href&#xff1a;指定访问资源的URL(统一资源定位符)* target&#xff1a;指定打开资源的方式* _self:默认值&#xff0c;在当前页面打开* _blank&#xff1a;在空白页面打开示例代码如下: <!--超链接 a-->…

完全卸载软件及电脑软件残留

当我在控制面板的卸载软件里没有发现我要删软件的软件时&#xff0c;只找到了软件的部分安装目录删除&#xff0c;就会发生残留问题&#xff0c;导致软件依旧可以运行。 经过这么多次后&#xff0c;我就找到了我自认为的最优解。首先运行这个软件&#xff1b;之后打开windows任…

[JavaWeb-CSS]CSS基础选择器

选择器&#xff1a;筛选具有相似特征的元素 * 分类&#xff1a;1. 基础选择器1. id选择器&#xff1a;选择具体的id属性值的元素.建议在一个html页面中id值唯一* 语法&#xff1a;#id属性值{}2. 元素选择器&#xff1a;选择具有相同标签名称的元素* 语法&#xff1a; 标签名称{…

真的是计划赶不上变化吗?

「做事容易半途而废&#xff0c;缺乏毅力」&#xff0c;我想100个人里面有99个是这样。更加好玩的是&#xff0c;这99个人里面可能还有不少会劝说别人要坚持……所以&#xff0c;其实我们每个人心里都清楚&#xff0c;一件事情不会“自动完成”&#xff0c;只有靠自己坚持做下去…

关于解决Path被大改,无法直接编辑恢复的问题

为了给eclipse改版&#xff0c;不用更改API&#xff0c;我动了环境变量&#xff0c;因为我的环境变量名称是path&#xff0c;所以当给tomcatTomCAT安装以及使用详细解释配置环境时&#xff0c;我直接新建的Path&#xff0c;把原来的path覆盖掉了&#xff0c;而且在注册表里无法…

[JavaWeb-HTML]CSS与html结合方式

CSS的使用&#xff1a;CSS与html结合方式 1. 内联样式* 在标签内使用style属性指定css代码* 如&#xff1a;<div style"color:red;">hello css</div>2. 内部样式* 在head标签内&#xff0c;定义style标签&#xff0c;style标签的标签体内容就是css代码* …

Sql Server之旅——第十二站 对锁的初步认识

作为一个开发人员&#xff0c;锁机制也是我们程序员必须掌握的东西&#xff0c;很久之前在学习锁的时候&#xff0c;都是教科书上怎么说&#xff0c;然后我怎么背&#xff0c;缺少一个工具让我们眼见为实。。。如果这样的话&#xff0c;学习一个东西就很容易忘记。。。因为这些…

算法基础

目录枚举例题应用&#xff1a;模拟技巧递归$分治递归分治算法贪心常见题型与动态规划的区别例题&#xff1a;应用排序选择排序冒泡排序插入排序计数排序基数排序二分最大值最小化STL 的二分查找三分法最大化平均值&#xff08;01分数规划&#xff09;枚举 枚举&#xff08;英语…

ABP框架 v2.9发布!

ABP框架和ABP商业版2.9已经发布,这是3.0之前的最后一个版本! 这篇文章将涵盖本次发布中的新增内容.ABP框架2.9有哪些新增内容&#xff1f;你可以中GitHub的发行说明中看到所有的变更.这篇文章将只包括重要特征/变更.预编译Razor Pages在之前的版本, 预构建的页面(应用模块)和视…

[JavaWeb-CSS]CSS概述

CSS&#xff1a;页面美化和布局控制 1. 概念&#xff1a; Cascading Style Sheets 层叠样式表* 层叠&#xff1a;多个样式可以作用在同一个html的元素上&#xff0c;同时生效2. 好处&#xff1a;1. 功能强大2. 将内容展示和样式控制分离* 降低耦合度。解耦* 让分工协作更容易*…

2021—ICPC省赛冲刺

OI-wiki 知识点汇总 算法基础 数论 动态规划专题 数据结构专题 数据结构详细 搜索相关 字符串相关 今天看见了别人的整理模板的博客&#xff0c;膜拜&#xff0c;orz&#xff0c;先放上来回头慢慢学习%%%%% 啥都有 超全模板题 板板板 逐步完善中。。。。