.NET AOT编译后能替代C++吗?

.NET AOT编译后能替代C++吗?

(1)开发 NDK 库有人进行尝试:NativeAOT-AndroidHelloJniLib (https://github.com/josephmoresena/NativeAOT-AndroidHelloJniLib) 通过修改 .Net Runtime 来生成 Android 下的 NDK Native 库。希望后续官方能直接支持。.

(2)Remobjects Elements (https://www.remobjects.com/elements/) 收费工具。可以采用 C# 语言开发 NDK Native 库,限制是不能自由使用 Core CLR,而是使用 Remobjects 的平台相关库 island 和跨平台库 RTL,RTL 库和 Core CLR 的相关类型接口相似。

Remobjects Elements 也可以用 C# 开发 wasm 模块,试用了下,满小巧的,gzip 下可以做到低于 1M,性能测试大约是 .net 6 的1/4-1/5。同上面一样,库这块会受到限制。

Remobjects Elements 的思路蛮有意思的,可以用 C#[或 Oxygene(Object Pascal), Swift, Java, Go or Mercury (VB)] 来写各种库或应用程序。Swift 语言的免费,其他的收费。可以用这些语言写native 程序、 .net 程序、 jvm 程序 ……

----

补充一下,这是个很值得探讨的问题,我一直在尝试用 csharp 代替 cpp。很多地方可以直接替代掉,但也遇到了一些问题:

(1)写底层算法或库的话,而调用者各种语言都有,csharp的,vb的,java 的,delphi 的都碰到过。那时的工作流程是,csharp 写算法,验证之后,再根据对方需求,改成 cpp。有 AOT 之后,这种情况可以避免掉。

(2)移动开发:安卓开发底层的库,参考资料太少。比如,开发个 sdk,给 java 调用,xamarin 资料太少,解决方案也不通用。

AOT 工具链完善后,对我来说,可以解决很多问题:

(1)写底层库:可以完全放心大胆的用 csharp 写底层库了,jit 模式下快速开发,aot 模式发布。csharp 的生产力比 cpp 高太多,构建系统也比 cmake 好使的多。我现在正在尝试优化 csharp 脚本开发 aot dll 的开发流程,目前还没完全完成,但也挺舒服的,以一个最简单的例子来说明,

lib.csx:

using System.Runtime.InteropServices;

[UnmanagedCallersOnly(EntryPoint = "add")]
public static int Add(int a, int b)
{
    return a + b;
}

[DllImport("pack/out/lib.dll")]
private static extern int add(int a, int b);

int result = add(2,4);
Console.WriteLine(result);

一行命令就编译成 dll 了,win10下是964 k。

nscript pack --aot --lib lib.csx

再用下面指令调用脚本:

nscript .\lib.csx

输出:

6

这种开发模式可比写 cpp 舒服太多了。目前 aot 工具链还不够完善,但是开发起来难度不大,就是工作量的问题。比如,根据代码,直接生成各个语言的调用封装,生成文档 ......

这样一来,就可以用 csharp 快速开发出底层库,生成动态链接库、静态链接库给桌面用,给服务器用,给移动设备用。底层库一般不牵扯到ui,其通用性极强。做 android 的 ndk 开发太折腾,cpp 开发,jni 调用,调试麻烦得很。

这也可以解决 csharp 这块防止反编译的问题,发布时也可以 UI 层用 jit 跑,底层用 aot 跑来保护代码。

(2)开发小工具

我造过一个轮子,把 avalonia 的控件库的核心给拆出来,用 direct2d 来绘制,弄了一些基本的控件,写简单的桌面程序,aot 之后尺寸可以控制在 4M,如果写一些小工具还是可以的。

再比如说,如果想发布一些 server 端小工具,以 http 方式提供服务,jit 模式下,用 asp.net 来做,单个文件发布压缩之后可以做到20-30M, 用 EmbedIO 来写的话,单个文件发布,win下11M,linux 下 13M。EmbedIO用了反射,aot 我没配好,没跑出来,根据经验的话,aot 应该可以做到 2-4 M。不过,11M 也够用了,来看看用脚本写的代码:

#r "nuget: EmbedIO, 3.4.3"

using EmbedIO;
using EmbedIO.WebApi;
using EmbedIO.Actions;
using EmbedIO.Files;

WebServer CreateWebServer(string url)
{
    var server = new WebServer(o => o
            .WithUrlPrefix(url)
            .WithMode(HttpListenerMode.EmbedIO))
        .WithLocalSessionManager()
        .WithStaticFolder("/static/", "./webroot", true, m => m.WithContentCaching(true)) // Add static files after other modules to avoid conflicts
        .WithModule(new ActionModule("/api", HttpVerbs.Get, HandleApi))
        .WithModule(new ActionModule("/", HttpVerbs.Any, ctx => ctx.SendDataAsync(new { Message = "Hello!" })))
        ;
    return server;
}

Task HandleApi(IHttpContext ctx)
{
    return ctx.SendDataAsync("api");
}

//@main
var url = "http://*:5000/";
var server = CreateWebServer(url);
server.RunAsync();
Console.ReadKey();

所以,工具链完善后,用 csharp 写东西很爽的:

(1)写完之后,AOT一下,给各个语言用 <--> 对标 cpp;

(2)写完之后,打包个几M 的小东西,扔服务器跑 <--> 是不是很像 golang?

(3)不建项目,像我上面那样用脚本写 <--> python;

(4)正常使用 <--> java;

(5)上 blazor <--> js。

除此之外,还有很新东西可以玩的 ......

----

一些场景替代不了:

(1)带 gc 的 .net aot,最小尺寸差不多1M,对于有的场景这个尺寸过大。比如说,对于插件系统,每个插件1M,100个就100M,过大,并且全部加载的话,有100个gc在跑,浪费。不知有没技术让100个插件共享一个 gc。虽然可以不开gc,但这样,就只是个大号的C了。

(2)对实时性要求高的场景,因为有gc存在会受到限制。

其他场景可以当有 gc 的 cpp 用,应该可以达到 cpp 80%+的性能。

其实现在jit,aot以及源生cpp性能都差不了太多,我做过密集计算的测试,细节忘记了,只记得是个 float 密集计算,java 的性能大于csharp和cpp。后来分析,jvm 做了向量化并行处理,csharp和cpp没有做。改csharp代码手动用simd,最后性能略高于Java。

aot的除了启动快,还有其他好处:(1)代码很难反编译;(2)尺寸小。

假设你要写个东西,给java调用或python调用,用 jit 的话,得几十M,aot 的话,可以做到 1M。这样的话,很多用cpp写的sdk,可以用csharp来写,aot编译成动态链接库,提供给上层用。

而不开gc的话,你可以就当他是个C语言的方言。

Windows以lib后缀名,Linux以a后缀 名)和动态库(Windows以dll为后缀名,Linux以so后缀名。本文顺序先从生成Native程序.然后是动态库,最后是静态库。