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。

  • 相关阅读:
    LSMW TIPS
    Schedule agreement and Delfor
    Running VL10 in the background 13 Oct
    analyse idoc by creation date
    New Journey Prepare
    EDI error
    CBSN NEWS
    Listen and Write 18th Feb 2019
    Microsoft iSCSI Software Target 快照管理
    通过 Microsoft iSCSI Software Target 提供存储服务
  • 原文地址:https://www.cnblogs.com/zzyDS/p/12165775.html
Copyright © 2011-2022 走看看