余亦真情感专家 發表於 2022-11-10 10:35:00

试试将.NET7编译为WASM并在Docker上运行

<p>之前有听到说Docker支持Wasmtime了,刚好.NET7也支持WASM,就带大家来了解一下这个东西,顺便试试它怎么样。</p>
<p>因为<strong>WASM</strong>(WebAssembly) 一开始是一个给浏览器的技术,比起JS解释执行,WASM能用于提升浏览器的用户体验,因为在一些场景中它有着比JS更好的性能。</p>
<p>大家可以将WASM理解为C#的MSIL或者Java的字节码,它并不是二进制代码,还是会由JIT编译执行,JIT有很多优化,另外大多数场景也只会JIT一次,加上省略了JS加载,语法分析各种的过程,才会有着比JS更好的性能。</p>
<p>另外因为WASM是中间码的格式,所以理论上任何语言C#、RUST、Java、Go都可以将代码编译为WASM,然后放到浏览器中执行。比如C#火热的Blazor项目,就是将C#编译为WASM,然后使C#代码能在浏览器中运行。</p>
<p>另外聊一聊<strong>WASI(WebAssembly System Interface)</strong>,我们知道WASM有着不错的可移植性和安全性(目前浏览器运行都是沙箱运行,对于权限管控很严格),那么就有一群大佬就说,我们是不是能脱离浏览器单独运行WASM程序呢?于是就产生了一个标准的系统接口,大家都按照这样的方式来生成WASM,调用系统API,然后我们开发一个Runtime,让大家的WASM程序都能在这上面运行。</p>
<p>举个不严谨的例子说明一下WASI就是比如:</p>
<ul>
<li>C# =&gt; MSIL =&gt; CLR(Mono、CoreCLR)</li>
<li>Java =&gt; 字节码 =&gt; JVM(HotSpot VM、ZingVM)<br>
而现在我们可以:</li>
<li>C# =&gt; WASM =&gt; WASI(wasmtime、wasmedge)。</li>
</ul>
<p>各位应该就明白了,WASI其实就是个运行时的规范,大家编译成WASM放上去就能跑。<br>
<img src="https://incerry-blog-imgs.oss-cn-hangzhou.aliyuncs.com/image-%e8%af%95%e8%af%95dotNet7%e7%9a%84WASM%e5%92%8cWASI-221108232119130.png" alt="" loading="lazy"></p>
<p>所以现在对于它的观点就是,觉得它在Server后端领域目前来说不是一个很价值的东西,因为可移植性好的语言比比皆是,比如C#、Java、Go等等。</p>
<p>拿性能来说,对于这样的中间语言性能无关就是JIT和GC,WASI的JIT和GC能做的像C#、Java这样的JIT、GC性能那么好吗?这个目前来说是存在疑问的,至少在短时间内很难追平其它平台十多年的优化。</p>
<p>再说WASM的另一个优点,就是体积小和启动快,现在C#支持NativeAOT、Java有GraalVM、Go和Rust之类的本身就是编译型语言,启动速度和体积都很不错,WASM在这个方面其实不占优势。</p>
<h2 id="net编译为wasm">.NET编译为WASM</h2>
<p>好了,言归正传,我们来试试.NET7上面的WASM。.NET7目前已经发布,我们需要使用最新的版本,如下图所示:<br>
<img src="https://incerry-blog-imgs.oss-cn-hangzhou.aliyuncs.com/image-%e8%af%95%e8%af%95%e5%b0%86dotNet7%e7%bc%96%e8%af%91%e4%b8%baWASM%e5%b9%b6%e5%9c%a8WASI%e4%b8%8a%e8%bf%90%e8%a1%8c-221109092059982.png" alt="" loading="lazy"></p>
<p>然后我们创建一个简单的控制台项目,用于输出斐波那契数列和执行耗时,代码如下所示 <strong>(这并不性能最优的实现,只是这样子实现简单)</strong>:</p>
<pre><code class="language-cs">using System.Diagnostics;

namespace PublishDotNetToWASM;

public static class Program
{
    public static void Main()
    {
      // warm
      ulong sum = 0;
      foreach (var i in Fibonacci().Take(1000))
      {
            sum += i;
      }

      // run
      sum = 0;
      var sw = Stopwatch.StartNew();
      foreach (var i in Fibonacci().Take(100000))
      {
            sum += i;
      }
      sw.Stop();
      Console.WriteLine($"Result:{sum}, Timespan:{sw.ElapsedTicks} Ticks");
    }

    private static IEnumerable&lt;ulong&gt; Fibonacci()
    {
      ulong current = 1, next = 1;

      while (true)
      {
            yield return current;
            next = current + (current = next);
      }
    }
}
</code></pre>
<p>接下来为了将.NET程序发布成WASM,我们需要安装<code>Wasi.Sdk</code>预览包,这个预览包是<code>Steve Sanderson</code>大佬做的支持,可以将.NET程序编译为WASM,截止至目前版本信息如下所示:</p>
<pre><code class="language-xml">&lt;PackageReference Include="Wasi.Sdk" Version="0.1.2-preview.10061" /&gt;
</code></pre>
<p>运行<code>dotnet publish -c Release</code>命令,将我们的应用程序发布为WASM格式,在发布过程中,需要下载<code>MinGW</code>作为编译器,网络环境不好的同学,需要使用proxy,稍微等待一会就顺利的发布成功了:<br>
<img src="https://incerry-blog-imgs.oss-cn-hangzhou.aliyuncs.com/image-%e8%af%95%e8%af%95%e5%b0%86dotNet7%e7%bc%96%e8%af%91%e4%b8%baWASM%e5%b9%b6%e5%9c%a8WASI%e4%b8%8a%e8%bf%90%e8%a1%8c-221109093936517.png" alt="" loading="lazy"></p>
<h2 id="运行wasm程序">运行WASM程序</h2>
<p>此时我们可以安装一下<code>Wasmtime</code>来执行我们的程序,通过<code>https://wasmtime.dev/</code>下载安装:<br>
<img src="https://incerry-blog-imgs.oss-cn-hangzhou.aliyuncs.com/image-%e8%af%95%e8%af%95%e5%b0%86dotNet7%e7%bc%96%e8%af%91%e4%b8%baWASM%e5%b9%b6%e5%9c%a8WASI%e4%b8%8a%e8%bf%90%e8%a1%8c-221109094423544.png" alt="" loading="lazy"></p>
<p>然后就可以直接使用<code>wasmtime</code>命令运行我们的程序,我分别使用<code>wasmtime</code>和<code>dotnet</code>运行了我们的程序:<br>
<img src="https://incerry-blog-imgs.oss-cn-hangzhou.aliyuncs.com/image-%e8%af%95%e8%af%95%e5%b0%86dotNet7%e7%bc%96%e8%af%91%e4%b8%baWASM%e5%b9%b6%e5%9c%a8WASI%e4%b8%8a%e8%bf%90%e8%a1%8c-221109095006851.png" alt="" loading="lazy"></p>
<p>可见目前来说WASM的性能还是惨不忍睹的,等一等后续的优化吧。</p>
<h2 id="将net发布到docker-wasi">将.NET发布到Docker WASI</h2>
<p>再来看看我们的Docker,对于Docker支持WASI我感到并不意外,因为Docker的容器化对于直接执行的WASM来说还是比较重,支持它是一个拓宽影响力的好事。具体的执行模型如下所示,对于WASM应用有着不同的执行方式。不再使用<code>runc</code>而是<code>wasmedge</code>。</p>
<p><img src="https://incerry-blog-imgs.oss-cn-hangzhou.aliyuncs.com/image-%e8%af%95%e8%af%95%e5%b0%86dotNet7%e7%bc%96%e8%af%91%e4%b8%baWASM%e5%b9%b6%e5%9c%a8WASI%e4%b8%8a%e8%bf%90%e8%a1%8c-221108232749175.png" alt="" loading="lazy"><br>
<code>wasmedge</code>也是一个实现了WASI标准的WASM运行时,和上文提到的wasmtime一样。</p>
<p>要实现在Docker上运行WASM程序需要安装Docker的预览版,链接<code>https://docs.docker.com/desktop/wasm/</code>。<br>
<img src="https://incerry-blog-imgs.oss-cn-hangzhou.aliyuncs.com/image-%e8%af%95%e8%af%95%e5%b0%86dotNet7%e7%bc%96%e8%af%91%e4%b8%baWASM%e5%b9%b6%e5%9c%a8Docker%e4%b8%8a%e8%bf%90%e8%a1%8c-221109105327473.png" alt="" loading="lazy"></p>
<p>然后我们整一个Dockerfile,我们直接依赖scratch镜像即可,因为它不需要其它的基础镜像(暂时我没有使用.NET7的多段构建镜像,听大佬说目前貌似有问题)。</p>
<pre><code class="language-dockerfile">FROM scratch
COPY ./bin/Release/net7.0/PublishDotNetToWASM.wasm /PublishDotNetToWASM.wasm
ENTRYPOINT [ "PublishDotNetToWASM.wasm" ]
</code></pre>
<p>再使用下面的命令构建Docker镜像,由于是wasm镜像,所以需要带额外的参数。</p>
<pre><code class="language-sh">docker buildx build --platform wasi/wasm32 -t publishdotnettowasm .
</code></pre>
<p>可以看到打包出来的镜像是非常小的,只有3.68MB。<br>
<img src="https://incerry-blog-imgs.oss-cn-hangzhou.aliyuncs.com/image-%e8%af%95%e8%af%95%e5%b0%86dotNet7%e7%bc%96%e8%af%91%e4%b8%baWASM%e5%b9%b6%e5%9c%a8Docker%e4%b8%8a%e8%bf%90%e8%a1%8c-221109132801243.png" alt="" loading="lazy"></p>
<p>运行的话也很简单,用下方的命令即可,需要指定runtime为<code>io.containerd.wasmedge.v1</code>,另外也需要指定paltform。</p>
<pre><code class="language-shell">docker run --rm --name=publishdotnettowasm --runtime=io.containerd.wasmedge.v1 --platform=wasi/wasm32 publishdotnettowasm
</code></pre>
<p>我把dotnet原生运行、wasmtime运行、docker WASI运行都跑了一下,可以发现目前来说性能是惨不忍睹。<br>
<img src="https://incerry-blog-imgs.oss-cn-hangzhou.aliyuncs.com/image-%e8%af%95%e8%af%95%e5%b0%86dotNet7%e7%bc%96%e8%af%91%e4%b8%baWASM%e5%b9%b6%e5%9c%a8Docker%e4%b8%8a%e8%bf%90%e8%a1%8c-221109133136384.png" alt="" loading="lazy"></p>
<h2 id="总结">总结</h2>
<p>以上就是如何将.NET7程序发布到WASM,然后在Docker最新的WASI中运行的样例,目前来看基本的运行都已经OK,不过正如前面提到的,现在性能还是太受影响了。</p>
<p>这不仅仅是在.NET平台上,其它语言Rust、C、C++编译为WASM上都有明显的性能下降。<br>
<img src="https://incerry-blog-imgs.oss-cn-hangzhou.aliyuncs.com/image-%e8%af%95%e8%af%95%e5%b0%86dotNet7%e7%bc%96%e8%af%91%e4%b8%baWASM%e5%b9%b6%e5%9c%a8Docker%e4%b8%8a%e8%bf%90%e8%a1%8c-221109142651992.png" alt="" loading="lazy"></p>
<p>思来想去可能在一些<strong>插件化和不需要性能很好</strong>的场景WASI会比较用。不过这些都需要时间慢慢见证,毕竟存在即合理,像JS这样的语言不一样好好的?</p>
<p>我们可以拭目以待,看看WASM/WASI会不会给我们带来其它惊喜,期待后续Steve Sanderson大佬和WASM社区的相关优化。</p>
<h2 id="源码链接">源码链接</h2>
<p>https://github.com/InCerryGit/PublishDotNetToWASM</p>
<h2 id="参考文献">参考文献</h2>
<p>https://www.docker.com/blog/docker-wasm-technical-preview/<br>
https://www.zhihu.com/question/304577684/answer/1961085507<br>
https://arghya.xyz/articles/webassembly-wasm-wasi/<br>
https://laurentkempe.com/2022/10/31/experimenting-with-dotnet-7-wasm-and-wasi-on-docker/</p><br><br>
来源:https://www.cnblogs.com/InCerry/p/PublishDotNetToWASM.html
頁: [1]
查看完整版本: 试试将.NET7编译为WASM并在Docker上运行