网站设计网站维护,高端网站建设的要求,重庆门户网站华龙网,提供东莞网站建设价格一#xff1a;背景1. 讲故事在我们的一个全内存项目中#xff0c;需要将一家大品牌店铺小千万的trade灌入到内存中#xff0c;大家知道trade中一般会有订单来源,省市区 #xff0c;当把这些字段灌进去后#xff0c;你会发现他们特别侵蚀内存#xff0c;因为都是字符串类型… 一背景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.List1[[ConsoleApp6.Trade, ConsoleApp6]])
从上面输出中可以看到托管堆有200200 20w(程序分配) 200(系统分配)个然后再看size 16097216/1024/1024 15.35M这就是展示的所有原始情况。二压缩技巧分析1. 使用字典化处理其实在托管堆上有20w个字符串但你仔细观察一下会发现其实就是4种状态的重复显示要么一淘要么淘宝。。。这就给了我优化机会何不在获取数据的时候构建好OrderFrom的字典然后在trade中附增一个TradeFromID记录字典中的映射值因为特征值少用byte就可以了有了这个思想可以把代码修改如下class Program{public static Dictionaryint, string orderfromDict new Dictionaryint, 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.List1[[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.List1[[ConsoleApp6.Trade, ConsoleApp6]])
观察后发现当用了驻留池之后空间为 8497368 /1024/1024 8.1M你可能有疑问为什么和字典化相比内存要大24%呢仔细观察你会发现当用驻留池后ListTrade 中的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 TradeIDk__BackingField00007ff9eb2b59c0 4000002 8 System.String 0 instance 000001f8a1516030 TradeFromk__BackingField0:000 !DumpObj /d 000001f8a1516030
Name: System.String
String: WAP
可以看到, 000001f8a1516030 就是 堆上 stringWap的引用地址这个地址占用了8byte空间。再回头dump一下使用字典化方式的Trade,可以看到它是没有 TradeFromk__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 TradeIDk__BackingField00007ff9eb2b7d20 4000003 c System.Byte 1 instance 0 TradeFromIDk__BackingField
三总结大家可以根据自己的情况使用使用驻留池方式是改变最小的简单粗暴自己构建字典化虽然最省内存但需要修正业务逻辑这个风险自担哦。。。