Introduction:

  ※本文不是在描述旧版本Unity中mono编译器导致的foreach语句额外装箱错误

  博主是一名Unity 3D游戏开发者,游戏使用C#+lua开发,最近在优化C#代码时,发现了一处使用foreach不恰当的地方,其结果是造成了每帧近3k的GC Alloc,如此高频率的GC堆内存分配,会导致垃圾回收的调用更加频繁,从而影响游戏性能,而这只需要简单的修改即可避免;

  ※使用.Net 2.0的Unity版本,如果是较新的.Net 4.x版本,由于FCL实现修改,本文中48->56

  原始声明代码如下:

private readonly IDictionary<int, MyClass> mDic = new Dictionary<int, MyClass>();

  在每帧逻辑里面,会多次对其进行遍历,遍历代码如下:

foreach(var keyValuePair in mDic)
{
//do...
}

  通过Unity自带的Profiler分析,可以发现其导致的GC Alloc:

Body:

  通过上面的Profiler可以发现此时foreach语句实际上调用的是Dictionary定义中隐式实现的IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()方法,该方法的声明如下:

IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
{
return new Enumerator(this, Enumerator.KeyValuePair);
}

  其中,Enumerator是在Dictionary类中定义的嵌套结构类型:

  结构类型隐式转换为接口类型时会发生装箱,对于该Enumerator类型,其装箱后大小为16(开销字节)+8(字段dictionary)+8(字段next+stamp)+16(字段current:8(int字节对齐)+8)=48字节,调用29次即产生48*29=1392字节的堆内存分配,这符合我们看到Profiler里面看到的GC Alloc;

  为了解决这个问题,只需要将变量声明时改为Dictionary即可,不使用接口类型的变量,即:

private readonly Dictionary<int, MyClass> mDic = new Dictionary<int, MyClass>();

  此时,在对mDic进行foreach循环时,就会调用Dictionary<TKey,TValue>.GetEnumerator()方法,该方法返回值类型即结构类型的Enumerator,避免了装箱操作:

One more thing:

  这里可能很多人有个误解,即foreach是只能对实现了IEnumerable或IEnumerable<T>的类型对象进行遍历,其实不然,foreach语句还可以对满足以下条件的任何类型的对象进行遍历:

  实现了可访问的GetEnumerator()方法,且该方法的返回值类型符合:包含可访问的Current属性和bool MoveNext()方法;

Conclusion:

  这样就知道了,.Net框架类库提供的泛型集合类型都实现了这样的方法,因此可以放心对泛型集合进行foreach遍历,而不产生堆内存的分配,也因此,我们在使用这些类型时,尽量避免直接对其接口类型的变量进行遍历;


如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的认可是我写作的最大动力!

作者:Minotauros
出处:https://www.cnblogs.com/minotauros/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

C#效率优化(3)-- 使用foreach时避免装箱的更多相关文章

  1. C#效率优化(1)-- 使用泛型时避免装箱

    本想接着上一篇详解泛型接着写一篇使用泛型时需要注意的一个性能问题,但是后来想着不如将之前的详解XX系列更正为现在的效率优化XX系列,记录在工作时遇到的一些性能优化的经验和技巧,如果有什么不足,还请大家 ...

  2. php程序效率优化的一些策略小结

    php程序效率优化的一些策略小结   1.在可以用file_get_contents替代file.fopen.feof.fgets等系列方法的情况下,尽量用 file_get_contents,因为他 ...

  3. php效率优化

    php效率优化 最近在公司一边自学一边写PHP程序,由于公司对程序的运行效率要求很高,而自己又是个新手,一开始就注意程序的效率很重要,这里就结合网上的一些资料,总结下php程序效率优化的一些策略:1. ...

  4. C#效率优化(4)-- 编译器对数组遍历的优化

    在平时开发过程中,数组是我们使用频率最高的类型之一,在使用定长列表时,数组可以说是最佳方案,这也是我们最熟悉的数据结构之一. 在C#中使用数组,可以获取在内存上连续的相同类型的一组变量,在连续访问时可 ...

  5. jquery选择器效率优化问题

    jquery选择器效率优化问题   jquery选择器固然强大,但是使用不当回导致效率问题: 1.要养成将jQuery对象缓存进变量的习惯 //不好的写法 $('#btn').bind("c ...

  6. php性能效率优化

    [size=5][color=Red]php性能效率优化[/color][/size] 最近在公司一边自学一边写PHP程序,由于公司对程序的运行效率要求很高,而自己又是个新手,一开始就注意程序的效率很 ...

  7. Jenkins Kubernetes Slave 调度效率优化小记

    Jenkins K8S Slave 调度效率优化 by yue994488@126.com 使用kubernetes为测试工具Gatling进行大规模压测,压测期间发现Jenkins调度压测实例较慢, ...

  8. 见招拆招-PostgreSQL中文全文索引效率优化

    * { color: #3e3e3e } body { font-family: "Helvetica Neue", Helvetica, "Hiragino Sans ...

  9. Unity3d代码及效率优化总结

    1.PC平台的话保持场景中显示的顶点数少于200K~3M,移动设备的话少于10W,一切取决于你的目标GPU与CPU. 2.如果你用U3D自带的SHADER,在表现不差的情况下选择Mobile或Unli ...

随机推荐

  1. 虚拟机下 centos7 无法连接网络

    [root@localhost ~]# cd /etc/sysconfig/network-scripts [root@localhost network-scripts]# ls ifcfg-ens ...

  2. concurrent.futures模块(进程池/线程池)

    需要注意一下不能无限的开进程,不能无限的开线程最常用的就是开进程池,开线程池.其中回调函数非常重要回调函数其实可以作为一种编程思想,谁好了谁就去掉 只要你用并发,就会有锁的问题,但是你不能一直去自己加 ...

  3. 九校联考_24OI——餐馆restaurant

    凉心模拟D1T1--最简单的一道题 TAT 餐馆(restaurant) 题目背景 铜企鹅是企鹅餐馆的老板,他正在计划如何使得自己本年度收益增加. 题目描述 共有n 种食材,一份食材i 需要花ti 小 ...

  4. [C#.net]ListBox对Item进行重绘,设置背景色和前景色

    别的不多说了,上代码,直接看 首先设置这行,或者属性窗口设置,这样才可以启动手动绘制,参数有三个 Normal: 自动绘制 OwnerDrawFixed:手动绘制,但间距相同 OwnerDrawVar ...

  5. LOJ-10100(割点个数)

    题目链接:传送门 思路: 就是求割点的个数,直接Tarjan算法就行. 注意输入格式(判断比较水). #include<iostream> #include<cstdio> # ...

  6. myeclipse 自动部署web项目(自动编译)

    打开自动编译:project->build automatically; 注:以下两种方法适用tomcat配置在myeclipse中的情况. 1.如果在myeclipse中tomcat是以deb ...

  7. IPC,Hz(Hertz) and Clock Speed

    How do we measure a CPU's work? Whether it's fast or not depends on three factors: IPC, Hz, Clock sp ...

  8. Docker环境安装与配置

    Docker 简介 Docker使用Go语言编写的 安装Docker推荐LInux内核在3.10上 在2.6内核下运行较卡(CentOS 7.X以上内核是3.10) Docker 安装 安装yum-u ...

  9. attempt to open datawindow failed@安装两个PB软件

    attempt to open datawindow failed@安装两个PB软件 安装了不同版本的PB软件后,默认打开方式为后一个安装的版本. 如果想改为第一个安装版本,在“右键”——>”打 ...

  10. Delphi控件cxGrid 如何动态创建列?

    方法一: var i: Integer; Column: TcxGridDBColumn; cxView: TcxGridDBTableView; begin cxView := Self.Level ...