DotNetAnywhere:可供选择的 .NET 运行时

我最近在收听一个名为DotNetRock 的优质播客,其中有以Knockout.js而闻名的Steven Sanderson 正在讨论 " WebAssembly And Blazor "。

也许你还没听过,Blazor 正试图凭借WebAssembly的魔力将 .NET 带入到浏览器中。如果您想了解更多信息,Scott Hanselmen 已经在 " .NET和WebAssembly——这会是前端的未来吗? "一文中做了一番介绍。( 点击查看该文的翻译)。

尽管 WebAssembly 非常酷炫,然而更让我感兴趣的是 Blazor 如何使用DotNetAnywhere作为底层的 .NET 运行时。本文将讨论DotNetAnywhere 是什么,能做什么,以及同完整的 .NET Framework 做比较。


DotNetAnywhere

首先值得指出的是,DotNetAnywhere (DNA) 被设计为一个完全兼容的 .NET 运行时,可以运行被完整的.NET 框架编译的 dll 和 exe 。除此之外 (至少在理论上) 支持 以下的.NET 运行时的功能,真是令人激动!

  • 泛型

  • 垃圾收集和析构

  • 弱引用

  • 完整的异常处理 - try/catch/finally

  • PInvoke

  • 接口

  • 委托

  • 事件

  • 可空类型

  • 一维数组

  • 多线程

另外对于反射提供部分支持

  • 非常有限的只读方法
    typeof(), GetType(), Type.Name, Type.Namespace, Type.IsEnum(),

最后,还有一些目前不支持的功能:

  • 属性

  • 大部分的反射方法

  • 多维数组

  • Unsafe 代码

各种各样的错误或缺少的功能可能会让代码无法在 DotNetAnywhere下运行,但其中一些已经被Blazor 修复,所以值得时不时检查 Blazor 的发布版本。

如今,DotNetAnywhere 的原始仓库不再活跃 (最后一个持续的活动是在2012年1月),所以未来任何的开发或错误修复都可能在 Blazor 的仓库中执行。如果你曾经在 DotNetAnywhere 中修复过某些东西,可以考虑在那里发一个PR。

更新:还有其他版本的各种错误修复和增强:

  • https://github.com/ncave/dotnet-js

  • https://github.com/memsom/dna

源代码概览

我觉得 DotNetAnywhere 运行时最令人印象深刻的一点是 只由一个人开发,并且 只用了 40,000 行代码!反观,完整的 .NET 框架仅是垃圾收集器就有将近37000 行代码 ( 更多信息请我之前发布的CoreCLR 源代码漫游指南 )。

机器码 - 共 17,710 行

LOCFile
3,164JIT_Execute.c
1,778JIT.c
1,109PInvoke_CaseCode.h
630Heap.c
618MetaData.c
563MetaDataTables.h
517Type.c
491MetaData_Fill.c
467MetaData_Search.c
452JIT_OpCodes.h

托管代码 - 共 28,783 行

LOCFile
2393corlib/System.Globalization/CalendricalCalculations.cs
2314corlib/System/NumberFormatter.cs
1582System.Drawing/System.Drawing/Pens.cs
1443System.Drawing/System.Drawing/Brushes.cs
1405System.Core/System.Linq/Enumerable.cs
745corlib/System/DateTime.cs
693corlib/System.IO/Path.cs
632corlib/System.Collections.Generic/Dictionary.cs
598corlib/System/String.cs
467corlib/System.Text/StringBuilder.cs

关键组件

接下来,让我们看一下 DotNetAnywhere 中的关键组件,正是我们了解怎么兼容 .NET 运行时的好办法。同样我们也能看到它与微软 .NET Framework 的差异。

加载 .NET dll

DotNetAnywhere 所要做的第一件事就是加载、解析包含在 .dll 或者.exe 中的 元数据和代码。这一切都存放在MetaData.c中,主要是在LoadSingleTable(..) 函数中。通过添加一些调试代码,我能够从一般的 .NET dll 中获取所有类型的 元数据 摘要,这是一个非常有趣的列表:

MetaData contains     1 Assemblies (MD_TABLE_ASSEMBLY)MetaData contains     1 Assembly References (MD_TABLE_ASSEMBLYREF)MetaData contains     0 Module References (MD_TABLE_MODULEREF)MetaData contains    40 Type References (MD_TABLE_TYPEREF)MetaData contains    13 Type Definitions (MD_TABLE_TYPEDEF)MetaData contains    14 Type Specifications (MD_TABLE_TYPESPEC)MetaData contains     5 Nested Classes (MD_TABLE_NESTEDCLASS)MetaData contains    11 Field Definitions (MD_TABLE_FIELDDEF)MetaData contains     0 Field RVA's (MD_TABLE_FIELDRVA)MetaData contains     2 Propeties (MD_TABLE_PROPERTY)MetaData contains    59 Member References (MD_TABLE_MEMBERREF)MetaData contains     2 Constants (MD_TABLE_CONSTANT)MetaData contains    35 Method Definitions (MD_TABLE_METHODDEF)MetaData contains     5 Method Specifications (MD_TABLE_METHODSPEC)MetaData contains     4 Method Semantics (MD_TABLE_PROPERTY)MetaData contains     0 Method Implementations (MD_TABLE_METHODIMPL)MetaData contains    22 Parameters (MD_TABLE_PARAM)MetaData contains     2 Interface Implementations (MD_TABLE_INTERFACEIMPL)MetaData contains     0 Implementation Maps? (MD_TABLE_IMPLMAP)MetaData contains     2 Generic Parameters (MD_TABLE_GENERICPARAM)MetaData contains     1 Generic Parameter Constraints (MD_TABLE_GENERICPARAMCONSTRAINT)MetaData contains    22 Custom Attributes (MD_TABLE_CUSTOMATTRIBUTE)MetaData contains     0 Security Info Items? (MD_TABLE_DECLSECURITY)

更多关于 元数据 的资料请参阅 介绍 CLR 元数据,解析.NET 程序集—–关于 PE 头文件 和 ECMA 标准 等文章。


执行 .NET IL

DotNetAnywhere 的另一大功能是 "即时编译器" (JIT),即执行 IL 的代码,从 JIT_Execute.c和JIT.c 中开始执行。在 JITit(..) 函数 的主入口中 "执行循环",其中最令人印象深刻的是在一个 1,374 行代码的 switch 中就有 200 多个 case !!

从更高的层面看,它所经历的整个过程如下所示:


与定义在 CIL_OpCodes.h (CIL_XXX) .NET IL 操作码 ( Op-Codes)  不同,DotNetAnywhere JIT 操作码 (Op-Codes) 是定义在 JIT_OpCodes.h (JIT_XXX)中。

有趣的是这部分 JIT 代码是 DotNetAnywhere 中唯一一处使用汇编编写 ,并且只是 win32 。 它允许使用 jump 或者 goto 在 C 源码中跳转标签,所以当 IL 指令被执行时,实际上并不会离开 JITit(..) 函数,控制(流程)只是从一处移动到别处,不必进行完整的方法调用。

#ifdef __GNUC__#define GET_LABEL(var, label)
var = &&label
#define GO_NEXT() goto **(void**)(pCurOp++)#else#ifdef WIN32#define GET_LABEL(var, label) \{ __asm mov edi, label \__asm mov var, edi }#define GO_NEXT() \{ __asm mov edi, pCurOp \__asm add edi, 4 \__asm mov pCurOp, edi \__asm jmp DWORD PTR [edi - 4] }#endif

IL 差异

在完整的 .NET framework 中,所有的 IL 代码在被 CPU 执行之前都是由  Just-in-Time Compiler (JIT) 转换为机器码。

如你所见, DotNetAnywhere "解释" (interprets) IL时是逐条执行指令,甚至会调用 JIT.c 文件来完成。 没有机器码 被反射发出 (emitted) ,所以这个命名还是有点奇怪!?

或许这只是一个差异,但实在是无法让我搞清楚它是如何进行 "解释" (interpreting) 代码和 "即时编译" (JITting),即使我再阅读完下面的文章还是不得其解!! (有人能指教一下吗?)

  • 即时编译器和解释器有什么区别?

  • 了解传统的解释器、JIT 编译器、JIT 解释器 和 AOT 编译器 的不同之处

  • JIT vs Interpreters

  • 为什么我们将 Java 字节码转换为机器码的东西称为 “JIT编译器” 而不是 “JIT解释器” ?

  • 了解 JIT 编译和优化


垃圾回收

所有关于 DotNetAnywhere 的垃圾回收(GC) 代码都在 Heap.c 中,而且还是 600 行易于阅读的代码。给你一个概览吧,下面是它暴露的函数列表:

void Heap_Init();
void Heap_SetRoots(tHeapRoots *pHeapRoots, void *pRoots, U32 sizeInBytes);void Heap_UnmarkFinalizer(HEAP_PTR heapPtr);
void Heap_GarbageCollect();U32 Heap_NumCollections();U32 Heap_GetTotalMemory();HEAP_PTR Heap_Alloc(tMD_TypeDef *pTypeDef, U32 size);HEAP_PTR Heap_AllocType(tMD_TypeDef *pTypeDef);
void Heap_MakeUndeletable(HEAP_PTR heapEntry);
void Heap_MakeDeletable(HEAP_PTR heapEntry);
tMD_TypeDef* Heap_GetType(HEAP_PTR heapEntry);
HEAP_PTR Heap_Box(tMD_TypeDef *pType, PTR pMem);
HEAP_PTR Heap_Clone(HEAP_PTR obj);
U32 Heap_SyncTryEnter(HEAP_PTR obj);
U32 Heap_SyncExit(HEAP_PTR obj);
HEAP_PTR Heap_SetWeakRefTarget(HEAP_PTR target, HEAP_PTR weakRef);HEAP_PTR* Heap_GetWeakRefAddress(HEAP_PTR target);
void Heap_RemovedWeakRefTarget(HEAP_PTR target);

GC 差异

就像我们对比 JIT/Interpreter 一样, 在 GC 上的差异同样可见。

Conservative GC

首先,DotNetAnywhere 的 GC 是 Conservative GC。简单地说,这意味着它不知道 (或者说肯定) 内存的哪些区域是对象的引用/指针,还是一个随机数 (看起来像内存地址)。而在.NET Framework 中 JIT 收集这些信息并存在GCInfo structure中,所以它的 GC 可以有效利用,而 DotNetAnywhere 是做不到。

相反, 在 标记(Mark) 的阶段,GC 获取所有可用的 " 根 (roots) ", 将一个对象中的所有内存地址视为 "潜在的" 引用(因此说它是 "conservative")。然后它必须查找每个可能的引用,看看它是否真的指向 "对象的引用"。通过跟踪 平衡二叉搜索树 (按内存地址排序) 来执行操作, 流程如下所示:

但是,这意味着所有的对象引用在分配时都必须存储在二叉树中,这会增加分配的开销。另外还需要额外的内存,每个堆多占用 20 个字节。我们看看 tHeapEntry 的数据结构 (所有的指针占用 4 字节, U8 等于 1 字节,而 padding 可忽略不计), tHeapEntry *pLink[2] 是启用二叉树查找所需的额外数据。

struct tHeapEntry_ {    // Left/right links in the heap binary treetHeapEntry *pLink[2];    // The 'level' of this node. Leaf nodes have lowest levelU8 level;    // Used to mark that this node is still in use.    
   // If this is set to 0xff, then this heap entry is undeletable.U8 marked;    // Set to 1 if the Finalizer needs to be run.  
   // Set to 2 if this has been added to the Finalizer queue    
   // Set to 0 when the Finalizer has been run (or there is no Finalizer in the first place)  
   // Only set on types that have a FinalizerU8 needToFinalize;    // unusedU8 padding;  
   // The type in this heap entrytMD_TypeDef *pTypeDef;    
   // Used for locking sync, and tracking WeakReference that point to this objecttSync *pSync;    // The user memoryU8 memory[0]; };

为什么 DotNetAnywhere 这样做呢?   DotNetAnywhere的作者Chris Bacon 是这样 解释:

告诉你吧,整个堆代码确实需要重写,减少每个对象的内存开销,并且不需要分配二叉树。一开始设计 GC 时没有考虑那么多,(现在做的话)会增加很多代码。这是我一直想做的事情,但从来没有动手。为了尽快使用 GC 而只好如此。 在最初的设计中完全没有 GC。它的速度非常快,以至于内存也会很快用完。

更多 "Conservative" 机制和 "Precise" GC机制的细节请看:

  • Precise 对比 conservative 以及内部指针

  • .NET CLR 如何区分托管指针和非托管指针?

GC 只做了 "标记-扫描", 不会做压缩

在 GC 方面另一个不同的行为是它不会在回收后做任何内存 压缩 ,正如 Steve Sanderson 在 working on Blazor 中所说:

在服务器端执行期间,我们实际上并不需要任何内存固定 (pin),在客户端执行过程中并没有任何互操作,所有的东西(实际上)都是固定的。因为 DotNetAnywhere 的 GC只做标记扫描,没有任何压缩阶段。

此外,当一个对象被分配给 DotNetAnywhere 时,只是调用了 malloc(), 它的代码细节在 Heap_Alloc(..) 函数 中。所以它也没有"Generations" 或者 "Segments" 的概念,你在 .NET Framework GC 中见到的如 "Gen 0"、"Gen 1" 或者 "大对象堆" 等都不会出现。


线程模型

最后,我们来看看线程模型,它与 .NET Framework 中的线程模型截然不同。

线程差异

DotNetAnywhere (表面上)乐于为你创建线程并执行代码, 然而这只是一种幻觉. 事实上它只会跑在 一个线程 中, 不同的线程之间 切换上下文:

你可以通过下面的代码了解, ( 引用自 Thread_Execute() 函数)将  numInst 设置为 100 并传入 JIT_Execute(..) 中:

for (;;) {U32 minSleepTime = 0xffffffff;I32 threadExitValue;status = JIT_Execute(pThread, 100);switch (status) {....}
}

一个有趣的副作用是 DotNetAnywhere 中corlib 的实现代码将变得非常简单。如Interlocked.CompareExchange() 函数的内部实现 所示, 你所期待的同步就缺失了:

tAsyncCall* System_Threading_Interlocked_CompareExchange_Int32(PTR pThis_, PTR pParams, PTR pReturnValue) {U32 *pLoc = INTERNALCALL_PARAM(0, U32*);U32 value = INTERNALCALL_PARAM(4, U32);U32 comparand = INTERNALCALL_PARAM(8, U32);*(U32*)pReturnValue = *pLoc;    if (*pLoc == comparand) {*pLoc = value;}    return NULL;
}

基准对比

作为性能测试, 我将使用C# 最简版本 实现的 基于二叉树的计算机语言基准测试做对比。

注意:DotNetAnywhere 旨在运行于低内存设备,所以不意味着能与完整的 .NET Framework具有相同的性能。对比结果时切记!!

.NET Framework, 4.6.1 - 0.36 seconds

Invoked=TestApp.exe 15
stretch tree of depth 16         check: 13107132768    trees of depth 4        check: 10158088192     trees of depth 6        check: 10403842048     trees of depth 8        check: 1046528512      trees of depth 10       check: 1048064128      trees of depth 12       check: 104844832       trees of depth 14       check: 1048544long lived tree of depth 15      check: 65535Exit code      : 0Elapsed time   : 0.36Kernel time    : 0.06 (17.2%)User time      : 0.16 (43.1%)
page fault #   : 6604Working set    : 25720 KB
Paged pool     : 187 KB
Non-paged pool : 24 KB
Page file size : 31160 KB

DotNetAnywhere - 54.39 seconds

Invoked=dna TestApp.exe 15
stretch tree of depth 16         check: 13107132768    trees of depth 4        check: 10158088192     trees of depth 6        check: 10403842048     trees of depth 8        check: 1046528512      trees of depth 10       check: 1048064128      trees of depth 12       check: 104844832       trees of depth 14       check: 1048544long lived tree of depth 15      check: 65535Total execution time = 54288.33 ms
Total GC time = 36857.03 msExit code      : 0Elapsed time   : 54.39Kernel time    : 0.02 (0.0%)User time      : 54.15 (99.6%)
page fault #   : 5699Working set    : 15548 KB
Paged pool     : 105 KB
Non-paged pool : 8 KB
Page file size : 13144 KB

显然,DotNetAnywhere 在这个基准测试中运行速度并不快(0.36秒/ 54秒)。然而,如果我们对比另一个基准测试,它的表现就好很多。DotNetAnywhere 在分配对象()时有很大的开销,而在使用结构时就不那么明显了。

 Benchmark 1 (using classes)Benchmark 2 (using structs)
Elapsed Time (secs)3.12.0
GC Collections9667
Total GC time (msecs)983.59439.73

最后,我要感谢 Chris Bacon。DotNetAnywhere 真是一个伟大的代码库,对于我们实现 .NET 运行时很有帮助。

原文地址:http://www.cnblogs.com/chenug/p/8436819.html


.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/322131.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringCloud Greenwich(二)注册中心之consul、Zuul和 gateway网关配置

本项目是搭建基于consul注册中心的springcloud,使用zuul网关和gateway网关 一、框架搭建 (1)项目结构 micro-service 服务提供者 zuul-gateway zuul网关 springcloud-gateway gateway网关 (2)环境 consul 1.9.0…

Actor-ES框架:消息发布器与消息存储器

消息发布器:Ray是基于Event Sourcing设计的ES/Actor框架,ESGrain状态(State)的修改、ESGrain之间的通信默认使用RabbitMQ通信。消息的发布器主要是RabbitPubESGrain。RabbitPub特性RabbitPub特性是RabbitMQ消息发布器。RabbitSub特…

consul的安装搭建

一、下载consul consul官网下载地址:https://www.consul.io/downloads 旧版本下载 consul 1.9.3直接下载地址: consul_1.9.3_windows_amd64.zip consul_1.9.3_linux_amd64.zip 二、安装 将consul_1.9.3_xxx.zip解压的xxx/consul目录 (1&…

使用xUnit为.net core程序进行单元测试

一. 导读为什么要编写自动化测试程序(Automated Tests)?可以频繁的进行测试可以在任何时间进行测试,也可以按计划定时进行,例如:可以在半夜进行自动测试。肯定比人工测试要快。可以更快速的发现错误。基本上…

jzoj6276-[Noip提高组模拟1]树【线段树,扫描线,倍增】

正题 题目大意 一棵树,若干个点对,求不包括任何一个点对的路径数量。 解题思路 我们考虑将不合法的方案在坐标系上表示。 我们先只考虑一个点对(x,y)(x,y)(x,y),若xxx和yyy没有祖先关系,则不合法的路径一个点在xxx的子树中&…

SpringCloud Greenwich(三)注册中心之zookeeper、Zuul和 gateway网关配置

本项目是搭建基于zookeeper注册中心的springcloud,使用zuul网关和gateway网关 一、框架搭建 (1)项目结构 micro-service 服务提供者 zuul-gateway zuul网关 springcloud-gateway gateway网关 (2)环境 zookeeper…

Metrics, tracing 和 logging 的关系

译者注Peter Bourgon原作: Metrics, tracing, and logging译者:吴晟原作发表时间: 2017年2月21日这是在OpenTracing和分布式追踪领域内广受欢迎的一篇博客文章。在构建监控系统时,大家往往在这几个名词和方式之间纠结。 通过这篇文…

快速序列化组件MessagePack介绍

简介MessagePack for C#(MessagePack-CSharp)是用于C#的极速MessagePack序列化程序,比MsgPack-Cli快10倍,与其他所有C#序列化程序相比,具有最好的性能。 MessagePack for C&#xff…

SpringCloud Greenwich(四)注册中心之eureka、Zuul和 gateway网关配置

本项目是搭建基于eureka注册中心的springcloud,使用zuul网关和gateway网关 一、框架搭建 (1)项目结构 eureka-server eureka注册中心 micro-service 服务提供者 zuul-gateway zuul网关 springcloud-gateway gateway网关 (…

【ASP.NET Core】给路由规则命名有何用处

上一篇中老周给伙伴们介绍了自定义视图搜索路径的方法,本篇咱们扯一下有关 URL 路径规则的名称问题。在扯今天的话题之前,先补充点东东。在上一篇中设置视图搜索路径时用到三个有序参数:{2}{1}{0},分别是 Area、Controller、Actio…

SpringCloud Greenwich(五)之nacos、dubbo、Zuul和 gateway集成

本项目是搭建基于nacos注册中心的springcloud,集成dubbo框架,使用zuul网关和gateway网关 一、框架搭建 (1)项目结构 micro-service 服务提供者 zuul-gateway zuul网关 springcloud-gateway gateway网关 class-provider dubo…

.NET/.NET Core中更清晰的堆栈跟踪

在基于异常的语言中,堆栈跟踪是用于诊断问题最重要的工具之一。在某些情况下,开发人员能得到的仅为一条简短的错误信息以及堆栈跟踪,尤其是当个人可识别信息(PII)约束限制了日志记录的内容时。随着任务并行库&#xff…

SpringCloud Greenwich(六)集成dubbo与openfeign的feignTargeter报错,cannot access its superinterface Targeter

一、现象 org.springframework.beans.factory.BeanCreationException: Error creating bean with name feignTargeter defined in class path resource [org/springframework/cloud/openfeign/FeignAutoConfiguration$HystrixFeignTargeterConfiguration.class]: Initializati…

一个开源的强类型客户端(.NET 中的 Open Fegin)— Rabbit Go

在做RabbitCloud(之前是一个RPC,现在是一个微服务框架)的时候往往避不开客户端代理,之前把这些客户端代理都算作服务框架不可缺少的一部分,随着后期的深入发现这些客户端代理其实可以互通,类似spring cloud…

SpringCloud Greenwich(七)集成dubbo先启动消费者(check=false),然后启动提供者无法自动发现注册

SpringCloud Greenwich集成dubbo先启动消费者(checkfalse),然后启动提供者无法自动发现注册问题。 官方说明:修复bug的提交时间 spring-cloud-starter-dubbo 2.2.4.RELEASE之前的版本都会有先启动消费者(checkfalse&am…

.NET Core 实现定时抓取博客园首页文章信息并发送到邮箱

前言大家好,我是晓晨。许久没有更新博客了,今天给大家带来一篇干货型文章,一个每隔5分钟抓取博客园首页文章信息并在第二天的上午9点发送到你的邮箱的小工具。比如我在2018年2月14日,9点来到公司我就会收到一封邮件,是…

Linux shell echo打印不出换行

一、现象 echo打印不出换行 指令 ps aux | grep python ps aux | grep python | xargs echo 运行结果: 二、使用参数-e echo一样打印不出换行 ps aux | grep python | xargs echo -e 运行结果: 三、使用参数-e和双引号包裹占位符 echo终于可以…

基于Citus和ASP.NET Core开发多租户应用

Citus是基于PsotgreSQL的扩展,用于切分PsotgreSQL的数据,非常简单地实现数据“切片(sharp)”。如果不使用Citus,则需要开发者自己实现分布式数据访问层(DDAL),实现路由和结果汇总等逻…

ASP.NET CORE 微服务(简化版)实战系列-没有比这性价比再高的实战课程了

ASP.NET CORE 微服务(简化版)实战系列,最后1天298,现在注册购买再减50。作者jesse 腾飞在2.14 早上我买了他的课程后,他才做了下面这个活动:作者jesse 腾飞花了大量的时间做了一个非常好的视频教程,我个人也…

祝大家狗年家庭事业旺旺旺

冒泡排序,选择排序,插入排序,快速排序,堆排序,归并排序,希尔排序,桶排序,基数排序新年帮您排忧解难。有向图,无向图,有环图,无环图,完…