zoukankan      html  css  js  c++  java
  • 通过IL Emit来创建类型的探究

      最近刚开始研究IL,起源是看到Odin内部源码创建一个Type使用了这种做法,当时好奇为什么要这么做。

      先丢出代码例子:

     1 class TestClass 
     2         {
     3             public TestClass() 
     4             {
     5                 mylist = new List<int>();
     6                 for(int i=0;i<100;i++) 
     7                 {
     8                     mylist.Add(i);
     9                 }
    10             }
    11 
    12             public int x = 5;
    13             public List<int> mylist;
    14         }
    测试类代码
     1 // Do Some Test
     2             int needCnt = 10000;
     3             var preTime = EditorApplication.timeSinceStartup;
     4             for(int i=0;i<needCnt;i++) 
     5             {
     6                 TestClass t = new TestClass();
     7             }
     8             var nowTime = EditorApplication.timeSinceStartup;
     9             Debug.Log("通常创建的时间 " + (nowTime - preTime));
    10 
    11             var type = typeof(TestClass);
    12             var constructor = type.GetConstructor(Type.EmptyTypes);
    13             var method = new DynamicMethod(type.FullName + "_FastCreator", type, Type.EmptyTypes);
    14 
    15             var il = method.GetILGenerator();
    16 
    17             il.Emit(OpCodes.Newobj, constructor);
    18             il.Emit(OpCodes.Ret);
    19 
    20             var fastCreator = (Func<TestClass>)method.CreateDelegate(typeof(Func<TestClass>));
    21             
    22             preTime = EditorApplication.timeSinceStartup;
    23             for(int i=0;i<needCnt;i++) 
    24             {
    25                 var t = constructor.Invoke(new object[] {});
    26             }
    27             nowTime = EditorApplication.timeSinceStartup;
    28             Debug.Log("构造函数Invoke并且不转型创建的时间 " + (nowTime - preTime));
    29 
    30             preTime = EditorApplication.timeSinceStartup;
    31             for(int i=0;i<needCnt;i++) 
    32             {
    33                 TestClass t = constructor.Invoke(new object[] {}) as TestClass;
    34             }
    35             nowTime = EditorApplication.timeSinceStartup;
    36             Debug.Log("构造函数Invoke创建的时间 " + (nowTime - preTime));
    37 
    38             preTime = EditorApplication.timeSinceStartup;
    39             for(int i=0;i<needCnt;i++) 
    40             {
    41                 TestClass t = fastCreator();
    42             }
    43             nowTime = EditorApplication.timeSinceStartup;
    44             Debug.Log("Emit创建的时间 " + (nowTime - preTime));
    测试代码逻辑

      测试结果如下(直接在Unity上跑的):

      看起来的结果就是,通过IL Emit的时间跟直接new一个的时间是同一个数量级别的,通过method.Invoke的时间要慢上一倍。

      个人猜测Odin选择这种做法来创建种种类型的原因如下:1. 时间上比Invoke要快 2. 只有Type信息无法调用new(猜测?)3. 方便制造成一个委托,下次再用。

      以后有更多心得了再补充。。。

    ---------------------------------------------------------------分割线----------------------------------------------------------------

      又稍微了解了一下,第二点应该是错误的,可以通过 TestClass t = (TestClass)Activator.CreateInstance(type); 来创建对应的类型,甚至可以传入额外的参数来调用其他的构造函数。然后重新跑了下,新的测试结果如下:

      可以看到的是,这种方法的时间开销也是蛮高的,不如IL Emit。

      然后看了一下别人的博客,看到如下的一句话:  

      另外,凡事要分两面,Emit的效率高是有条件的,Emit动态创建方法的代价通常在1ms左右(一个非常高的初始代价),如果反射次数相当的少,原始反射的效率比Emit要高,只有当这个被Emit的方法被调用了相当多次(例如1000次)以后,才能补偿掉这个初始代价而lz的例子正好作为反面例子,每次创建一个动态方法(高代价),然后执行一行数据,然后就抛弃这个花了很大代价得到的动态方法,重新再创建...

      大概理解了Odin为什么要采用这种做法,如上面所说的,把Emit动态创建的方法存在表里,下次可以直接调用,速度又快又避免了创建代价大的问题。Odin利用这种方法创建各种OdinDrawer。

  • 相关阅读:
    Java启动工程时,加载固定数据到Map中(不用每次访问数据库)
    Java删除文件夹和其子文件、文件的拷贝和剪切
    EasyExcel导入工具(SpringMVC下使用)
    web工程启动时,在一个类中延迟加载Bean,因为该Bean类可能还没被JVM加载
    ECharts访问后台,JSON格式返回数据实例
    Java 实现缓存,一个线程存,一个线程取
    IE浏览器使用VLC实时显示视频(海康、大华)
    Windows和Linux下 Java开发ping工具类
    Quartz定时器+Spring + @Autowired注入 空指针异常
    ubuntu 安装mysql
  • 原文地址:https://www.cnblogs.com/zzyDS/p/12165775.html
Copyright © 2011-2022 走看看