WebAssembly 在 Arm 与 x86 中的性能分析

2021/03/18 19:18
阅读数 385

作者:Michael Yuan

原文刊发于 infoq.com,微信不支持外链,请至文末点击阅读原文,查看文中所附资源


随着基于 Arm 的高性能 CPU 越来越多地应用于移动设备之外,对于开发者来说,了解 Arm 在常见服务端软件堆栈中的性能特征至关重要。本文将使用 AWS 的 Arm(Graviton2) 和 x86_64 (Intel) EC2 实例来评估不同软件运行时,包括 Docker、Node.js 和 WebAssembly 的计算性能。


结论是,Arm 在云中更具成本效益,尤其是在与底层操作系统接近的轻量级运行时中。

背景

麻省理工学院教授 Leiserson 和 Thompson 等人在最近在 Science 杂志发表的一篇研究论文《There’s plenty of room at the Top: What will drive computer performance after Moore’s law?》讨论了当今计算机工程中最重大的挑战之一,摩尔定律走向终结。像 GPU、CPU 这样的计算机硬件已经达到了量子极限,无法再让其更快或更小。这阻碍了 40 年来以生产力和经济增长为动力的技术创新。我们所熟知的技术革命要就此终结了吗?

 

论文对计算机技术的未来持乐观态度。作者认为,软件上的提升可以代替摩尔定律,并在未来几年推动生产率的增长。为了说明这一点,他们证明了将机器学习算法从 Python 改用 C/本机代码重写,可以提高 60000 倍的性能!

 

然而,我们不能放弃现代软件运行时及其带来的开发者生产力的提升。1990 年代的前 Java 时代,用编译过的本地代码运行每个应用程序。如今的开发者依赖于高级编程语言、工具,特别是内存安全和可移植的运行时,来交付高质量的软件产品。根据这篇论文作者的说法,软件性能工程采用的方法是消除软件臃肿,并将软件定制到更高效的硬件上。

 

本文中,我们将通过在云计算场景中采用轻量级、高效的软硬件基础设施来评估性能的提升。具体而言,我们将在基于 Arm 的高能效 CPU (AWS Graviton2)和 Intel x86 CPU 上运行几个轻量级的软件运行时。

 

出于此次研究的目的,我们将重点放在单线程性能上。大多数 web 应用框架默认运行“每个请求一个线程”。从用户的角度来看,web 服务的性能很可能受到单个 CPU 执行速度的限制。这是一个有意简化的测试用例,用于演示原始的性能。

 

我们选择的 benchmark 如下:

  • 下面两个 benchmark 评估冷启动性能。

    • nop 测试启动应用程序环境并退出。

    • cat-sync 测试打开一个本地文件,向其中写入 128KB 的文本,然后退出。它评估进行操作系统调用时的性能。

  • 下面四个 benchmark 来自《Computer Languages Benchmarks Game》。《Computer Languages Benchmarks Game》为超过 25 种编程语言提供了各种 benchmark 程序。程序启动后,这四个 benchmark 评估运行时性能。

    • nbody 重复了 5 千万次,是一个 n-body 模拟。

    • fannkuch-redux 重复 12 次,测量索引访问整数数列。

    • mandelbrot 重复 15000 次,生成可移植的曼德布洛特集位图(Mandelbrot set portable bitmap)文件。

    • binary-trees 重复 21 次,分配和释放大量的 binary tree。


接下来,让我们看看确切的测试设置和一些性能数字!所有测试用例的源代码和脚本都可以在 GitHub 上获取。

 

源代码和脚本:https://github.com/second-state/wasm32-wasi-benchmark

 

软件臃肿问题改善

为了保证软件的安全性、安全性和跨平台可移植性,我们在容器和虚拟机中运行 benchmark 测试。最流行的容器运行时之一是 Docker,它已经对性能进行了优化。为了评估软件堆栈性能,我们在 AWS t3.small 实例上运行了以下测试用例, AWS t3.small 实例中有一个由 2 个 vCPU 组成的物理 CPU 核。我们使实例闲置的时间足够长,以积累足够的 CPU 余额,从而在整个外部性能测试中维持 100% 的 CPU 峰值。

 

测试用例 #1:为了模拟 web 应用的性能,我们将 benchmark 测试作为运行在 Docker 内部的 Node.js JavaScript 应用程序运行。

 

测试用例 #2:我们还在 Docker 中使用 Ubuntu Server 20.04 LTS 运行 benchmark 测试 C/C + + 本地应用程序。这种情况有点不现实,因为很少有人能够将他们的应用程序编译成单一的二进制可执行文件,并且忽略了像 Node.js 这样的运行时提供的工具和库的生态系统。但是这个用例可以作为我们在 Docker 下能达到的性能的比较点。

 

测试用例 #3:我们在 WebAssembly 虚拟机 SSVM 中运行 benchmark 测试。这些程序是用 Rust 编写的,并且编译成了 WebAssembly 字节码。SSVM 提供了运行时安全性、基于功能的安全性、可移植性以及与 Node.js 的集成。

 

基于能力的安全性要求应用程序拥有并显示授权 token 以访问受保护的资源。在使用 SSVM 的情况下,应用程序必须明确声明资源,例如文件系统文件夹,它需要在启动时访问这些资源。这个设计称为 WebAssembly 系统接口 (WASI)

 

我们用于测试用例的软件堆栈如下:

  • 运行在 EC2 实例的 Amazon Linux 2

  • Docker 19.03.6-ce

  • Docker 内部的 Ubuntu Server 20.04 LTS

  • Docker 内部的 Node.js v14.7.0

  • 本机可执行文件是由 LLVM 10.0.0 和 Clang 工具链编译的。在 Intel 架构中,我们使用了 Clang 的 -O3 flag(参见本节)。在 Graviton2 上,我们使用 AWS 推荐的 LLVM 优化设置 -march=armv8.2-a+fp16+rcpc+dotprod+crypto (见下一节)。

  • 使用 AOT (Ahead-of-Time 编译器)优化的 SSVM 0.6.4


Intel 架构的结果如下图所示。所有数字表示以秒为单位的执行时间。数字越小表示性能越好。


注意: SSVM 冷启动比 Docker 快几个数量级,因此我们将冷启动时间乘以 50 倍,以方便查看。对于只是偶尔调用的微服务来说,冷启动问题尤其重要。例子包括许多响应偶尔事件的 serverless 函数,每个函数调用都可能涉及运行时的冷启动。



关键要点:

  • SSVM 冷启动时间小于 20 毫秒,而 Docker 需要 700 毫秒或更多。 SSVM 显然快 30 倍。

  • 对于计算密集型的运行时任务,Docker + native 和 SSVM 约比 Docker + Node.js 快两倍。

  • Docker + native 是一个糟糕的选择,因为它的性能没有 SSVM 好,同时也损失了 Node.js 和 JavaScript 的生态系统优势。

 

SSVM 中的程序甚至比本机代码运行得更快。这是如何实现的呢?SSVM 在运行时采用了提前编译(Ahead-of-Time,AOT)技术。它允许编译器专门针对当前运行的机器进行优化,而不是针对整个 CPU 架构类型进行通用优化。

 

从操作系统级容器(如 Docker )切换到 应用级虚拟机 (例如, SSVM) 可以显著提高性能。虽然 SSVM 确实需要对 Node.js 应用程序进行一些修改,但仍然可以为开发者提供运行时安全性、可移植性和完整的 Node.js 生态系统。

高效的硬件

Leiserson 和 Thompson 等教授提出的软件性能工程解决方案并不仅仅是消除现有软件臃肿,还要求更好地利用硬件设备的软件。经过多年在高度受限的计算环境中的迭代设计,例如在手机上,Arm 架构提供了一个高效运行通用软件程序的独特机会。AWS 在优化 Arm 的服务器端虚拟化方面做出了开创性的工作,基于 AWS Gravtion2 的 Amazon EC2 实例非常有潜力进一步提高 web 应用程序性能。在本节中,我们重复了 AWS t3.small (x86) 上和 t4g.small (基于 Arm 的 Graviton2) 实例 的 benchmark 测试。它们配置相似,但 t4g.small (Arm)每小时的成本便宜约 24%。

  • t3.small 实例类型采用 Intel Xeon Platinum 8000 系列处理器(x86_64) ,持续的 Turbo CPU 时钟速度最高可达 3.1 GHz。t3 实例提供了 2 个运行在 1 个物理内核上的 vCPU 和 2GB 内存。每小时花费 0.0208 美元。

  • t4g.small 实例类型采用 AWS Graviton2 CPU (Arm64 或 aarch64) ,时钟速度为 2.5 GHz。t4g 实例提供了 2 个运行在 2 个物理内核上的 vCPU 和 2GB 内存。每小时花费 0.0168 美元。

AWS Graviton2 处理器为多线程应用程序提供了额外的性能优势。但是,正如我们前面讨论的那样,本文仅测试 benchmark 算法的单个线程实现。

 

Intel 和 Graviton2 的 benchmark 测试结果如下图所示。所有数字都表示以秒为单位的执行时间。数字越小表示性能越好

 


考虑到 EC2 中不同类型 CPU 的 CPU 时间和每小时费率,成本效益方面的 benchmark 结果如下图所示。所有数字都是以 0.001 美分为单位运行 benchmark 操作的成本。数字越小表明单位成本的性能越好

 


 关键要点:

  • 在这两个 CPU 平台上,SSVM 仍然以比 Docker 快 100 倍的冷启动时间,比 Docker + Node.js (即 mandelbrot 基准)快 5 倍的运行时速度获得最佳性能。

  • 与 Intel x86 CPU 相比,Graviton2 性价比更佳。

  • Graviton2 在运行本地二进制代码时,性能显著优于 Intel。

  • Graviton2 和 Intel 之间的 Node.js 和 SSVM 的性能比较难分上下。但是考虑到 Graviton2 实例便宜 24% ,Gravtion2 在成本性能比方面遥遥领先。

 

我们假设基本的 Linux 操作系统已经针对 ARM CPU 进行了优化,允许本地二进制文件充分从 Graviton2 的性能特性中获益。然而,由于 Arm 在服务器和云空间中的相对新颖性,在栈中处于更高位置的框架和运行时软件,如 Node.js 和 SSVM,并没有专门针对 Arm CPU 进行优化。Arm 版本的服务器端软件仍然有很大的改进空间。

我们得到的经验

在这篇文章中,我们比较了在不同的计算机体系结构上常用的算法和 web 应用程序任务。我们还比较了传统栈 Docker、 Node.js 与新栈 SSVM (WebAssembly) 的性能,并观察了性能提升最高 100 倍(冷启动部分)和最高 5 倍(运行时部分)的情况。但这依然还有很大的改进空间,特别是对基于 Arm 的 CPU 的软件优化。

 

在后摩尔定律时代,技术可以通过改进软件组合继续引导社会生产力的增长。在顶部有很大的提升空间


点击阅读原文,查看英文原文。

本文分享自微信公众号 - WebAssembly 中文社区(webassemblywasm)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部