前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >高性能动态编译库Natasha发布1.0版本!

高性能动态编译库Natasha发布1.0版本!

作者头像
心莱科技雪雁
修改2019-08-12 10:50:58
4560
修改2019-08-12 10:50:58
举报
文章被收录于专栏:雪雁的专栏雪雁的专栏

文章转载于公众号【NCC开源社区】,作者NMSAzulx

一、 前言

对于开源贡献者,Emit和表达式树不是陌生的字眼,IL的动态特性为封装工作带来了极大的方便,会Emit的开发者可以说驾驭了大部分的高性能、高动态的编程技巧。纵观ef、dapper、json.net等第三方常用库,哪个能脱离emit而独善其身,也正因如此,幸福了一批批懒癌患者,包括我这个懒癌中晚期患者(这里给各位病友问好),与此同时本人对封装有着莫名其妙的执念,就在两支怪力的驱使下走上了对emit的不归路.

旧版Natasha始于2016年,当时是对Emit进行的封装,中途经有柠檬的提醒完善了UT和兼容性等工作,后由Victor.X.Qu补充了文档,后经ORM实战。

二、Emit非银弹

经历过重重思考和实践,Emit不是动态的最佳实践,简单的从以下几个角度来讲:

  • 调优:
    • dup : emit中的dup指令优化在是由开发者控制的,在熟悉指令操作的同时又给开发者带来了额外的优化工作。
    • if/while/for :不得不说IL可以透过代码看本质,指令就是这样的,在条件分支上,标签跳转的形式使得逻辑执行灵活多变。这样除了栈的操作之外,还要关注标签的位置和跳转语句的优化,另外还要清晰的记得你的各个分支。
    • 并发字典与算法优化 :这一点是出自我的极端,在对象成员的赋值/加载等操作面前,并发字典像是一场灾难,卖尽气力优化的动态执行,却被某些数据结构所糟蹋。至于算法与动态编译结合起来,应该没几个病友做过,各位如果有兴趣的话可以慢慢体会。
  • 兼容性:
    • 结构体 : 类与结构体在操作指令上有着诸多的不同,开发者不仅仅要熟悉对类的操作指令,还要对结构体做出兼容,诸如ldflda、 ldloca、Constrained等指令,对于开发者来说并不是一件省心的事。
    • 类型转换 : .NET中的类型转换不仅仅有指令级的转换,standard还提供了诸多方法支持不同类型之间的转换,因此你还需要花一些功夫去处理这些。
    • 语法糖 : 一切语法糖在emit面前都要还原,比如可空类型语法糖,对象比较语法糖,类型比较语法糖等等,无疑会大大增加兼容工作的负担(core3.0的可空引用我还没有做测试)。
  • 构建难度:
    • 深度克隆 : 深度克隆是动态编程的一个典型实战,如果各位病友坚持用EMIT挑战的话,可以没病走两步,走两步。
    • 深度构建 :一旦遇到了动态构建动态场景,那么这个复杂度难以想象。
    • 猜错误 : Emit并没有很好的友情提示,没有语法检查,而被程序锻炼成老猎手一定要付出很多代价。
  • 维护升级:
    • 后续开发 :接手emit代码是一件令人纠结的事,当量变引起质变的时候,从兴奋到苦不堪言这种事情并不是没有发生过,尤其是现在.NET开源工作者都比较独立,没有凝聚力和氛围,人的生命以及精力是有限的。
    • 传承 :由上面诸多信息也可见,在新技术的冲击下,在令人不安的环境下,在孤独的夜里,传承也是个问题。

尽管表达式树已经帮我们做了一些工作,但复杂场景和使用习惯仍然封印着开发者的大脑。

三、狙击暴君

Roslyn到如今已经耳熟能详了,编译被当作成服务对外开放,让不少开发者从中受益,但由于文档不全,实例不充分,从开始一直到2018年期间,对于懒癌开发者来说,基于Roslyn开发都是一件憋手的事情(例如一些必备操作文档,在2019年今年5月份才提上日程)。Natasha使用Roslyn做为编译引擎,不仅仅在动态构建上进行了人性化升级,还在功能上进行了简化。您不仅可以使用Natasha轻松的构建类、结构体、方法、接口、抽象类,还可以轻松的继承类、重载方法、实现接口、抽象类等等,技术较新,仅支持.standard2.0。

项 目 地 址:https://github.com/dotnetcore/Natasha

Nuget索引:DotNetCore.Natasha (正式版1.0.0.0)

(娜塔莎)(原型苏联红军第25步兵师的中尉柳德米拉·帕夫利琴科,一名出色的女狙击手)

使用Natasha你需要关注:
  1. 在您的工程文件里添加这个节点:<PreserveCompilationContext>true</PreserveCompilationContext>
  2. 了解wiki中反解器的概念及使用。
  3. 注意命名空间,自动补充命名空间目前尚未支持,需要您手动操作,使用using方法添加。
  4. 想尽一切办法拼接字符串,目前符合CSharp7.3或以下C#版本的都行。
  5. 编译模式有区分:StreamComplier内存流编译/FileComplier文件流编译, 文件流编译的内容,可以被动态调用。当你想动态编译类B的时候使用类A,那类A就需要使用文件流编译,相当于dll动态加载到运行时。
  6. 使用Natasha中的Operator来构建你的动态内容。

四、性能

这几年随着.NET架构引擎的不断升级,dynamic、emit执行性能已经得到了大幅度提升,roslyn也不例外,之前官方给过性能测试截图,上面显示是比emit快一点,个人的基准测试要等下一个benchmark版本,从耗时的角度来说roslyn <= emit (roslyn有指定release模式编译),所以大家根本不用关心性能问题。

五、使用案例

使用之前需要注意的是,方法操作都是基于内存流编译,类和其他都基于文件流编译。但有特殊情况,比如有些方法需要被复用,这时可以在编译选项中如此操作:

var tempBuilder = FastMethodOperator.New;
tempBuilder.ComplierOption.UseFileComplie();

 //可根据需求选择编译方式
 var builder = new ClassBuilder();
 builder.ComplierOption.UseFileComplie();
 builder.ComplierOption.UseMemoryComplie();
  • 实现抽象类与接口
public abstract class TestAbstract
{
        public int Name;
        public int Age;
        public abstract int GetAge();
        public abstract string GetName();
}

OopOperator<TestAbstract> abstractBuilder = new OopOperator<TestAbstract>();
abstractBuilder.ClassName("UTestClass");
abstractBuilder["GetName"] = "return Name;";
abstractBuilder["GetAge"] = "return Age;";
abstractBuilder.Compile();
TestAbstract test = abstractBuilder.Create("UTestClass");




public interface ITest 
{
    int MethodWidthReturnInt();
    string MethodWidthReturnString();
    void MethodWidthParamsRefInt(ref int i);
    string MethodWidthParamsString(string str);
    string MethodWidthParams(int a,string str,int b);
}


OopOperator<ITest> interfaceBuilder = new OopOperator<ITest>();
interfaceBuilder.ClassName("UTestClass");
interfaceBuilder["MethodWidthReturnInt"] = "return 123456;";
interfaceBuilder["MethodWidthReturnString"] = "return \"test\";";
interfaceBuilder["MethodWidthParamsRefInt"] = "i+=10;";
interfaceBuilder["MethodWidthParamsString"] = "return str+\"1\";";
interfaceBuilder["MethodWidthParams"] = "return a.ToString()+str+b.ToString();";
interfaceBuilder.Compile();
ITest test = interfaceBuilder.Create("UTestClass");
  • 快速编写委托
public delegate string GetterDelegate(int value);
     

//方法一
var action = DelegateOperator<GetterDelegate>.Create("value += 101; return value.ToString();");
//action(1); result: "102"


//方法二
var action = "value += 101; return value.ToString();".Create<GetterDelegate>();
//action(1); result: "102"
  • 定制方法
var action = FastMethodOperator.New
             .Param<string>("str1")
             .Param(typeof(string),"str2")
             .MethodBody("return str1+str2;")
             .Return<string>()
             .Complie<Func<string,string,string>>();
                    
string result = action("Hello ","World!");    
//result:   "Hello World!"
  • 伪造方法
//这里只为演示,实际使用请用下面的静态构造,拿到委托后可以直接运行。

public class TestB
{
     public void TestMethod(){}
}


 var action = FakeMethodOperator.New
                .UseMethod<TestB>("TestMethod")
                .MethodContent($@"Console.WriteLine(""Hello World!"");")
                .Complie<Action>();
         
//The class script :
//
//   using System;
//   public class N20d26dcba7e6451eaf4c4a6f4753e243
//   {
//          public void TestMethod()
//         {
//               Console.WriteLine("Hello World!");
//          }
//   }


 var action = FakeMethodOperator.New
                .UseMethod<TestB>("TestMethod")
                .StaticMethodContent($@"Console.WriteLine(""Hello World!"");")
                .Complie<Action>();


//The class script :
//
//    using System;
//    public static class Neae0b5f6b8b94f4b9418ebc68813760b
//    {
//         public static void TestMethod()
//         {
//              Console.WriteLine("Hello World!");
//         }
//    }
  • 自定制一个类
ClassBuilder builder = new ClassBuilder();
var script = builder
             .Namespace("TestNamespace")
             .ClassAccess(AccessTypes.Private)
             .ClassModifier(Modifiers.Abstract)
             .ClassName("TestUt2")
             .ClassBody(@"public static void Test(){}")
             .PublicStaticField<string>("Name")
             .PrivateStaticField<int>("_age")
             .Builder().Script;

Assert.Equal(
@"using System;namespace TestNamespace{private abstract class TestUt2{public static String Name;private static Int32 _age;public static void Test(){}}}"
, script);
自编译一个类
string text = @"using System;
using System.Collections;
using System.Linq;
using System.Text;
 
namespace HelloWorld
{
    public class TestIndex1
    {
        public string Name;
        public int Age{get;set;}
    }
    public class TestIndex2
    {
        public string Name;
        public int Age{get;set;}
    }

    public class TestIndex3
    {
        public string Name;
        public int Age{get;set;}
    }
}

namespace HelloWorld{

    public struct TestStruct1{}
    public struct TestStruct2{}
    public class TestIndex4
    {
        public string Name;
        public int Age{get;set;}
    }
}";


//根据脚本创建动态类
//寻找第二个命名空间中的第一个类
Type type = RuntimeComplier.GetClassType(text, 1,2);
Assert.Equal("TestIndex4", type.Name);


//寻找第二个命名空间中的第二个结构体
type = RuntimeComplier.GetStructType(text, 2, 2);
Assert.Equal("TestStruct2", type.Name);

六、方便的扩展

  • 使用Natasha的类扩展:
Example:          
         
         
        typeof(Dictionary<string,List<int>>[]).GetDevelopName();
        //result:  "Dictionary<String,List<Int32>>[]"
        
              
        typeof(Dictionary<string,List<int>>[]).GetAvailableName();
        //result:  "Dictionary_String_List_Int32____"
        
           
        typeof(Dictionary<string,List<int>>).GetAllGenericTypes(); 
        //result:  [string,list<>,int]
        
        
        typeof(Dictionary<string,List<int>>).IsImplementFrom<IDictionary>(); 
        //result: true
        
        
        typeof(Dictionary<string,List<int>>).IsOnceType();         
        //result: false
        
        
        typeof(List<>).With(typeof(int));                          
        //result: List<int>
  • 使用Natasha的方法扩展:
Example:  

        Using : Natasha.Method; 
        public delegate int AddOne(int value);
        
        
        var action = "return value + 1;".Create<AddOne>();
        var result = action(9);
        //result : 10
        
        
        var action = typeof(AddOne).Create("return value + 1;");
        var result = action(9);
        //result : 10
  • 使用Natasha的克隆扩展:
Example:

        Using : Natasha.Clone;
        var instance = new ClassA();
        var result = instance.Clone();
  • 使用Natasha的快照扩展:
Example:

        Using : Natasha.Snapshot;
        var instance = new ClassA();
        
        instance.MakeSnapshot();
        // ********
        //  do sth
        // ********
        var result = instance.Compare();

七、实战

  • 深度克隆:Natasha中提供了类遍历器,结合多层动态编译,可以支持复杂数据结构的克隆操作。
  • NCaller是Natasha的实战项目,采用动态原生操作+动态优化查找算法,可以对动/静态类初始化以及字段和属性的常规操作,耗时仅为原生的2.5倍以下。

八、调试

由于核心编译引擎为Roslyn,因此语法检查、词法检查、语义检查等都支持,这样可以很好的为开发者提供错误提示, Natasha为此增加日志模块(NScriptLog), 在编译流程中,捕获编译信息并记录,另需注意的是,Natasha脚本的格式化操作与VS的格式化一样,所以需要开发者在构建脚本的时候就要多多注意换行的问题。Natasha的编译日志共有3个种类:成功日志、错误日志、警告日志。

  • 成功日志:
  • 错误日志:
  • 警告日志:

九、寄语

随着.NETCore的不断升级,Natasha还有很多新的特性在等待开发,希望各位多多支持。https://github.com/night-moon-studio NMS是一个基于Natasha的开源项目“孵化”组,项目成熟且通过审核之后(或改名)推荐进入NCC,大家可以积极参与。

Natasha如果说是一个库不如说是一个时机,动态封装已经不是学习成本高、开发效率低的工作了,希望各位能积极参与尝试开源项目,共同打造.NET Core新生态。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-08-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 麦扣聊技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章转载于公众号【NCC开源社区】,作者NMSAzulx
    • 一、 前言
      • 二、Emit非银弹
        • 三、狙击暴君
          • 四、性能
            • 五、使用案例
              • 六、方便的扩展
            • 七、实战
              • 八、调试
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档