一個 .NET 應用僅僅只是一塊在 .NET 運行時上面運行的二進位代碼。而 .NET 運行時只是一個能執行這項任務的程式。當前的 .NET Framework 和 .NET Core 運行時採用 C++ 編寫,而 Mobius 是一個使用 C# 重寫的 .NET 運行時,重寫包括 JIT 編譯和 ... ...
一個 .NET 應用僅僅只是一塊在 .NET 運行時上面運行的二進位代碼。而 .NET 運行時只是一個能執行這項任務的程式。當前的 .NET Framework 和 .NET Core 運行時採用 C++ 編寫,而 Mobius 是一個使用 C# 重寫的 .NET 運行時,重寫包括 JIT 編譯和 GC 等,這些邏輯都將和 C++ 無關
原文:Mobius – .NET runtime running on .NET Core – TooSlowException
我看到這個有趣的項目的時候就想試試安利一下大家,這個項目特別適合用來瞭解 GC (Garbage Collector 垃圾收集)和 JIT (Just-In-Time Compiler 即時編譯器)的演算法
讓 C# 編寫一個 .NET 運行時和編寫一個運行在這個運行時上的 .NET 應用是否有可能呢?換句話是不要 Native 的本機代碼或 C++ 代碼,所有的代碼都是通過 C# 編寫是否有可能?這看起來是一個無窮的遞歸,用 .NET 寫 .NET 的運行時運行在 .NET 的運行時上。這是不是就是將一個 .NET 運行時運行在另一個 .NET 運行時上?
作者kkokosa決定開始試試水,這就是做 Mobius 運行時想法的原因。這個想法聽起來很奇怪,連作者都不抱期望在一個世紀內將這個想法投入使用。不過作者的想法是想要瞭解如果寫出整個 .NET 運行時需要多少的代碼量。同時作者也發現了其實這個想法的作用其實很小,即使想象現在有一個 NuGet 包在安裝完成之後就可以添加到咱的應用上,此時的這個包就包含了完整的運行時代碼,其實好像也不能做什麼
原理
其實這個想法在其他的領域也有人嘗試過,最著名的不過是 RVM —— 用 Java 編寫的 JVM 虛擬機。雖然他需要使用 C 的引導啟動,但是能做到自己托管自己,完全由 Java 運行的虛擬機同時不需要其他的虛擬機。這看起來非常和作者想象的 Mobius 非常接近
這個想法不止作者一個人在想,其實也有小伙伴在 Github 上發佈了一個 issus 說能否使用 C# 寫 JIT 和 GC 的邏輯
基於這些考慮,可以看到開發 Mobius 的原因如下:
- 用於實驗和研究的框架。使用 C# 和 .NET 編寫的運行時,咱可以更簡易和快速的瞭解整個原型,比如對 JIT 或 GC 模塊的更改。咱可以使用熟悉的語言如 F# 等去編寫整個 .NET 的底層
- 用於學習。在寫這個框架或參與開發的時候,可以從裡面學到很多運行時的做法。這也是可以用到很多現代化的 C# 特性的項目,使用更底層的 API 如 Span staclallock Unsafe 等
- 提升性能。這顯然是很有爭議的一點。在另一個托管的運行時上面運行另一個運行時看起來就和高性能沒有關聯。但是如果應用是熱啟動,那麼意味著此時運行的代碼生成質量可以依托對CPU的優化,可以達到比本機代碼更好的性能。使用 C# 開發理論上可以使用更加穩健的優化。同樣用 C# 寫 GC 也能有相同的提升
- 用於玩鬧。對於很多人來說,例如德熙看著這個項目一步步搭建起來是十分有趣的
如上面說的,其實都不是很強的理由,為什麼要用 .NET 去寫 .NET 運行時。大多數情況下,人們會認為使用 C++ 開發和使用 C# 開發不是對立的,兩者的差別不是很大。作者非常同意這個觀點,這就是為什麼作者其實是將這個項目當成一個玩具和實驗的項目
先拋開是否有必要做這樣的事情,請讓咱想想這個項目可以如何做
基本設計原理
首先,要理解的最重要的事情是 Mobius 仍然會將咱的應用程式編譯為本地 Native 代碼。以這種方式,最終應用程式將以(幾乎)本機代碼速度運行。不同之處在於托管的基礎設施,如 GC 和類型系統、JIT編譯器是作為托管代碼運行的。這意味著這些代碼也被 JIT 編譯
如上圖,我們有兩層JIT構建的代碼和底層實際運行時的本地 Native 代碼。從圖片看起來中間的這一層 .NET Core 基礎設施的 Mobius 層是多餘的。如果這一層是使用無分配對象的方式寫的,那麼不需要任何的 GC 方法。在預熱之後,對 JIT 的調用也將會很少。這就允許咱假設在一個正常運行的應用程式中,大部分在 Mobius 層的內容都是經過了 JIT 編譯優化完成之後運行的,這包括了常用的對 .NET Core 代碼的 JIT 構建的代碼,這將十分接近 .NET Core 的原生調用
從上面的圖看,其實 Mobius 的多餘還是很明顯。一個可以想的方法是在兩個運行時之間共用基礎設施
重寫整個類型系統並不是一件很有趣的事情。我們甚至可以考慮在 Mobius 中重用相同的 GC 垃圾回收,所以使用 Mobius 給 .NET 應用提供對象將看起來不錯。雖然上面的方法請看起來不錯,但依然存在兩個問題:
- 這大大減少了 Mobius 框架需要研究的功能。因為沒有重新研究一遍 GC 和 JIT 演算法,我們將被迫考慮如何合併現有的技術
- 在 .NET 運行時裡面 JIT 和 GC 和類型系統都有比較大的耦合。除了在 Mobius 實現相同的機制之外沒有其他方法,將會受限於當前的方法
基於這個原因,作者認為 .NET Core 運行時應該只提供很少量的運行時服務給到 Mobius 框架,提供的服務主要只是調用 Jit 編譯代碼
當前狀態
當前作者還是試驗可行性,正在做的是讓最簡單的 C# 應用能玩起來
private static int Main(string[] args)
{
int num = 1;
int num2 = 2;
return num + num2;
}
通過一些可行性的測試,作者看到了曙光,應該是能做出來的。目前所有需要的機制都已就緒,包括:即使編譯的基礎支持,通過托管調用 JIT 代碼,通過 JIT 代碼調用 Mobius 框架。但是因為測試可行性的項目代碼寫的糟,還需要一點時間對代碼進行重構,完善並實現大量的元數據處理,去掉一些硬編碼值
現在這個可行性項目只是能做到運行當前這個簡單的應用而已,運行的時候通過完全的 CIL 指令和沒有任何的異常處理,同時只有 GC 的存根
在下一篇系列文章中,作者將介紹Mobius實現最底層部分的更多細節和代碼片段
逗比註:
如果本文看的不錯,想要參與開發,我覺得在這之前需要先讀一下農夫的書,請看 《.NET 底層入門》這本書
另外上面說的玩具什麼的只是原作者謙虛的說法,其實這個玩法是可行的,在 Github 有小伙伴在討論,請看 Port JIT and GC to C# 這個鏈接