Handling Dotnetanywhere: the. NET runtime is available for selection

Source: Internet
Author: User
Tags benchmark

Original: Dotnetanywhere:an alternative. NET Runtime
Author: Matt Warren
Translator: Zhang is very water

I've been listening to a high-quality podcast called Dotnetrock, and Steven Sanderson, famous for Knockout.js, is talking about "WebAssembly and Blazor."

Perhaps you have not heard that Blazor is trying to bring. NET into the browser with the magic of webassembly. If you would like more information, Scott Hanselmen is already on ". NET and webassembly--will this be the future of the front end? In this article. (Click to view the translation of this article).

Although WebAssembly is very cool, what interests me more is how Blazor uses Dotnetanywhere as the underlying. NET runtime. This article will discuss what Dotnetanywhere is, what it can do, and how to compare it with the complete. NET Framework.

Dotnetanywhere

First, it is worth noting that Dotnetanywhere (DNA) is designed to be a fully compatible. NET runtime that can run DLLs and EXE compiled by the full. NET Framework. Beyond that, it's exciting to support the following. NET runtime features (at least theoretically).

  • Generic type
  • Garbage Collection and destruction
  • Weak references
  • Complete exception Handling-try/catch/finally
  • PInvoke
  • Interface
  • Commissioned
  • Event
  • Nullable types
  • One-dimensional arrays
  • Multithreading

also provides partial support for reflection

  • Very limited read-only method
    typeof (), GetType (), Type.Name, Type.namespace, Type.isenum (),

Finally, there are some features that are not currently supported:

  • Property
  • Most of the reflection methods
  • Multidimensional arrays
  • Unsafe Code

A variety of errors or missing features may make the code impossible to run under Dotnetanywhere, but some of them have been blazor repaired, so it's worth checking out the release version of Blazor from time to time.

Today, Dotnetanywhere's original warehouse is no longer active (the last ongoing activity is in January 2012), so any future development or bug fixes may be performed in the Blazor repository. If you've ever fixed something in dotnetanywhere, you might consider sending a PR there.

Update: There are other versions of various bug fixes and enhancements:

    • Https://github.com/ncave/dotnet-js
    • Https://github.com/memsom/dna
Source Code Overview

I think the most impressive thing about the Dotnetanywhere runtime is that only one person developed it, and only 40,000 lines of code were used ! In reverse, the complete. NET framework is just a garbage collector with nearly 37000 lines of code (more info please let me previously publish the CORECLR Source Code roaming guide).

Machine code-Total 17,710 rows
LOC File
3,164 Jit_execute.c
1,778 Jit.c
1,109 Pinvoke_casecode.h
630 Heap.c
31] Metadata.c
563 MetaDataTables.h
517 Type.c
491 Metadata_fill.c
467 Metadata_search.c
452 Jit_opcodes.h
Managed code-Total 28,783 rows
LOC File
2393 Corlib/system.globalization/calendricalcalculations.cs
2314 Corlib/system/numberformatter.cs
1582 System.drawing/system.drawing/pens.cs
1443 System.drawing/system.drawing/brushes.cs
1405 System.core/system.linq/enumerable.cs
745 Corlib/system/datetime.cs
693 Corlib/system.io/path.cs
632 Corlib/system.collections.generic/dictionary.cs
598 Corlib/system/string.cs
467 Corlib/system.text/stringbuilder.cs
Key components

Next, let's look at the key components in Dotnetanywhere, which is a good way for us to understand how to work with the. NET Runtime. We can also see the difference between it and the Microsoft. NET Framework.

Loading. NET DLLs

The first thing Dotnetanywhere to do is load and parse the metadata and code contained in the. dll or . exe. all of this is stored in the METADATA.C, mainly in the loadsingletable (..) function. By adding some debugging code, I was able to get a summary of all types of metadata from a generic. NET DLL, which is a very interesting list:

MetaData contains 1 assemblies (md_table_assembly) MetaData contains 1 ASSEMBLY References (md_table_assemblyref) Me Tadata contains 0 Module References (md_table_moduleref) MetaData contains-Type References (md_table_typeref) Meta Data contains type definitions (MD_TABLE_TYPEDEF) MetaData contains-type specifications (MD_TABLE_TYPESPEC) Meta Data contains 5 Nested Classes (md_table_nestedclass) MetaData contains one Field definitions (md_table_fielddef) Meta    Data contains 0 Field RVA ' s (Md_table_fieldrva) MetaData contains 2 propeties (Md_table_property) MetaData contains Member References (Md_table_memberref) MetaData contains 2 Constants (md_table_constant) MetaData contains M      Ethod 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 Co Ntains 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 (M  D_table_genericparamconstraint) MetaData contains, Custom Attributes (md_table_customattribute) MetaData contains 0 Security Info Items? (md_table_declsecurity)

For more information about metadata , see introducing CLR metadata, resolving. NET assemblies--about PE header files and the ECMA standard.

Execute. NET IL

Another big feature of Dotnetanywhere is the "Instant Compiler" (JIT), the code that executes IL, which executes from jit_execute.c and jit.c. In the main portal of the Jitit (..) function "Execution loop", the most impressive is in a 1,374 lines of code in switch more than 200 case !!

At a higher level, the whole process it undergoes is as follows:

Unlike the definition in Cil_opcodes.h ( CIL_XXX ). NET IL opcode (op-codes), the dotnetanywhere JIT opcode (op-codes) is defined in Jit_opcodes.h ( JIT_XXX ).

Interestingly, this part of the JIT code is the only one in Dotnetanywhere that is written using the assembly, and just win32 . It allows you to use jump or goto jump tags in C source, so when the IL instruction is executed, it does not actually leave JITit(..) the function, and the control (process) is moved from one place to another without having to make a full method call.

#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 differences

In the full. NET framework, all IL code is converted from Just-in-time Compiler (JIT) to machine code before being executed by the CPU.

As you can see, dotnetanywhere "explain" (interprets) IL is executed on an instruction-by-article, or even called jit.c file. No machine code is reflected out (emitted), so this naming is still a bit strange!?

Maybe it's just a difference, but it's impossible for me to figure out how it's going to be interpreted (interpreting) code and "Instant Compile" (jitting), even if I finish reading the following article or not!! (Can someone enlighten me?)

    • What is the difference between an instant compiler and an interpreter?
    • Learn about the differences between the traditional interpreter, the JIT compiler, the JIT interpreter, and the AOT compiler
    • JIT vs Interpreters
    • Why do we convert Java bytecode to machine code something called the "JIT compiler" instead of "JIT interpreter"?
    • Understanding JIT Compilation and optimization
Garbage collection

All garbage Collection (GC) codes about Dotnetanywhere are in heap.c, and are 600 lines of code that are easy to read. To give you an overview, here is a list of the functions it exposes:

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 differences

As we compare jit/interpreter, the differences in GC are also visible.

Conservative GC

First, the GC for Dotnetanywhere is the conservative GC. Simply put, this means that it does not know (or is sure) which areas of memory are references/pointers to objects, or a random number (which looks like a memory address). And in the. NET Framework, JIT collects this information in gcinfo structure, so its GC can be used effectively, and dotnetanywhere is not.

Instead, at 标记(Mark) the stage, the GC gets all the available "roots (roots)" and treats all memory addresses in an object as "potential" references (so it is "Conservative"). Then it must look for each possible reference to see if it really points to "object references". Perform the operation by tracking the balanced binary search tree (sorted by memory address) as follows:

However, this means that all object references must be stored in the binary tree at allocation time, which increases the allocation overhead. Additional memory is required, with each heap taking up more than 20 bytes. We look at tHeapEntry the data structure (all pointers occupy 4 bytes, U8 equal to 1 bytes, and negligible padding ), which is the tHeapEntry *pLink[2] extra data needed to enable binary tree lookups.

struct tHeapEntry_ {    // Left/right links in the heap binary tree    tHeapEntry *pLink[2];    // The ‘level‘ of this node. Leaf nodes have lowest level    U8 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 Finalizer    U8 needToFinalize;        // unused    U8 padding;    // The type in this heap entry    tMD_TypeDef *pTypeDef;    // Used for locking sync, and tracking WeakReference that point to this object    tSync *pSync;    // The user memory    U8 memory[0];};

Why did Dotnetanywhere do it? Chris Bacon, author of Dotnetanywhere, explains this:

To tell you the whole heap of code does need to be rewritten, reducing the memory overhead of each object, and no need to allocate a binary tree. When you first design a GC, you don't think about that much, and you add a lot of code (now). It's something I've always wanted to do, but I never did. this is necessary to use the GC as soon as possible. in the original design, there was no GC at all. It is so fast that memory runs out quickly.

For more details on the "conservative" mechanism and the "precise" GC mechanism, see:

    • Precise contrast conservative and internal pointers
    • How does the. NET CLR differentiate between managed pointers and unmanaged pointers?
GC only does "tag-scan" and does not compress

Another different behavior in GC is that it does not compress any memory after recycling, as Steve Sanderson said in working on Blazor:

  • During server-side execution, we do not actually need any memory pinning (PIN), and there is no interoperability during client execution, and everything (in fact) is fixed. because Dotnetanywhere's GC only does a tag scan, there is no compression phase.

In addition, when an object is assigned to Dotnetanywhere, only malloc () is called, and its code details are in the Heap_alloc (..) function. So it does not have the concept of "generations" or "segments", as you can see in the. NET Framework GC, such as "Gen 0," "Gen 1," or "large Object heap".

Threading model

Finally, let's look at the threading model, which is very different from the threading model in the. NET Framework.

Thread differences

Dotnetanywhere (on the surface) is happy to create threads and execute code for you, but this is just an illusion. In fact it will only run in one thread and switch contexts between different threads:

You can learn from the following code that (the reference from the Thread_execute () function) is numInst set to 100 and passed JIT_Execute(..) in:

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

An interesting side effect is that corlib the implementation code in Dotnetanywhere will become very simple. As the Interlocked.CompareExchange() internal implementation of the function shows, the synchronization you expect is missing:

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;}
Benchmark comparison

As a performance test, I will contrast the binary tree-based computer language benchmark with the simplest version of C #.

Note: Dotnetanywhere is designed to run on low memory devices, so it does not mean that it has the same performance as the full. NET framework. When comparing the results, remember!!

. NET Framework, 4.6.1-0.36 seconds
Invoked=TestApp.exe 15stretch 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 KBPaged pool     : 187 KBNon-paged pool : 24 KBPage file size : 31160 KB
DotNetAnywhere-54.39 seconds
Invoked=dna TestApp.exe 15stretch 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 msTotal 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 KBPaged pool     : 105 KBNon-paged pool : 8 KBPage file size : 13144 KB

Obviously, Dotnetanywhere is not running fast in this benchmark (0.36 seconds/54 seconds). However, if we compare another benchmark, it behaves much better. Dotnetanywhere has a significant overhead when allocating objects ( ) and is less noticeable when used 结构 .

Benchmark 1 (using classes ) Benchmark 2 (using structs )
Elapsed Time (secs) 3.1 2.0
GC Collections 96 67
Total GC time (msecs) 983.59 439.73

Finally, I would like to thank Chris Bacon. Dotnetanywhere is really a great code base, and it's helpful for us to implement the. NET Runtime.

Please discuss this article in the/r/programming of Hacker News.

Handling Dotnetanywhere: the. NET runtime is available for selection

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.