zoukankan      html  css  js  c++  java
  • C#效率优化(3)-- 使用foreach时避免装箱

    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/

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

  • 相关阅读:
    星球居民突破 1800 人!
    测试数据管理
    解决InnoDB: Table mysql/innodb_index_stats has length mismatch in the column name table_name. Please run mysql_upgrade
    Warning: file_get_contents(): open_basedir restriction in effect. File(/proc/uptime) is not within the allowed path(s)解决方法
    Java终止线程的三种方式
    线程中断interrupt
    Linux 开启防火墙 避免非干系人误操作的处理
    Oracle12c 快速启动命令设置
    Docker 运行 Redis Rabbitmq seata-server ftp 的简单办法
    mysql8 CentOS7 简要安装说明
  • 原文地址:https://www.cnblogs.com/minotauros/p/10577149.html
Copyright © 2011-2022 走看看