亚马逊AWS官方博客

云上 ARM 实例应用优化之我见

ARM 处理器的崛起

过去两个月的科技媒体上关于ARM 芯片的新闻可谓是高潮迭起,不断的引起人们的关注。

  • 首先是在5月11日,AWS宣布了基于自研的Graviton 2处理器(使用了ARM Neoverse N1核心)的第六代EC2实例 – M6g正式发布。这似乎揭开了云计算市场上ARM处理器大规模应用的的序幕。
  • 紧接着,在今年6月23日的WWDC大会上,Apple公司宣布了一个影响深远的决定: 计划从2020年年底开始,Mac计算机将会从Intel芯片过渡到使用基于ARM的自研芯片。也许我们要问,ARM 处理器将将会在桌面设备上复制移动设备的成功吗?
  • 第三则新闻是关于高性能计算。6月22日发表的最新的一期TOP500榜单上,日本的 Fugaku系统以415.5千万亿次浮点运算的高性能LINPAC成绩成为TOP500 的第一名。而令人惊讶的是这是第一个使用ARM处理器的高性能处理系统。

林林总总,即使我们是半导体行业的门外汉也不难得出一个结论 – ARM 处理器不仅仅统治了手机、嵌入式应用这些传统的优势领域,或将在桌面系统、高性能计算尤其是云计算领域扮演越来越重要的角色。

EC2 上的ARM 处理器

以往我们熟悉的AWS所提供的的计算资源所使用的多为Intel® Xeon® 处理器,例如Skylake 、Ivy Bridge、Broadwell以及Haswell等Intel的多个系列的CPU。即使在2018年新出现的使用了AMD EPYC 处理器的新的实例类型(M5a、R5a以及T3a等),其CPU的架构体系与Intel 的CPU 也还同属 “x86-64”(也被称作x64、AMD64以及Intel 64等)体系架构。抛开Intel 与AMD半世纪的爱恨情仇,我们可以简单的把这些处理器视作一类。

而这一次M6g 实例上的处理器却与以往大有不同,采用的是一款名为Graviton 2 处理器,是由 AWS 使用 64 位 ARM Neoverse N1 内核定制而成。说起ARM处理器,我们所了解的是其在移动计算市场的所向披靡,却一直在桌面计算、服务器等对性能要求更高的市场中表现不佳。在过去数年中,ARM不止一次对高性能处理器领域发起挑战,但多以惨淡的结果收场。而转机就出现在2019年初ARM发表的Neoverse N1。对于这一次的发布,ARM的设计目标就是使其成为一个高性能架构,并重新调校了微架构使其能够以更高的频率运行。在这一点上,ARM与AMD、Intel的路线有所不同,后两家面向高性能平台的处理器产品受制于功耗、面积等因素,频率相比消费级产品要低。但是在N1的设计中却恰好相反,处理器的频率相对更高。不仅如此,Neoverse N1还有一些独特设计,例如缓存的设计。N1中的L1数据缓存和指令缓存部分都是64KB、4-Way设计。其中最重要的改变是整个缓存完全采用了一致性设计,它大幅度简化了虚拟环境的实现并且极大地提高了性能。而且这一设计对ARM在超大规模计算中保持竞争力也是必须的,因为这可以很方便地扩展核心数量。而这一点在Graviton2 处理器上表现的淋漓尽致。与第一代 AWS Graviton 处理器相比,Graviton2 处理器实现了性能和功能飞跃,性能提升 7 倍、计算内核数量增加 4 倍,缓存增加 2 倍,内存速度提升 5 倍。

总体来看,Graviton 2 与N1平台差异不大,且采用了TSMC的7nm工艺。当然细微差异还是存在的。例如Graviton 2的CPU内核的时钟频率较N1要低一些。我自己运行一个简单的测试程序得到的Gravitino 2 时钟频率约为2.5GHz,并且L3缓存为32MB而不是公版的64MB。该系统由8通道DDR-3200内存控制器支持,并且SoC支持64个PCIe4通道用于I/O。至于这款芯片的功耗,考虑到ARM宣称的64核2.6GHz CPU的功耗约为105W,以及Ampere最近披露的其80核3GHz N1服务器芯片的功耗为210W。ANANDTECH 对Graviton 2给出的的估算结果是功耗大致位于80瓦至之110瓦之间。

现代的CPU 存在着核心数量越来越多的趋势。随着系统中的核心数量的增多,服务器芯片中的内存性能成为了影响性能至关重要的因素。借助8个DDR4-3200内存控制器, Graviton 2芯片具有先进的内存功能,理论上可提供高达204GB/s的峰值带宽。在ANANDTECH 的测试中, Graviton2的单个CPU内核能够以高达36GB/s的速度传输写入数据。内存加载速度高达18.3GB/s,内存复制达到了令人印象深刻的29.57GB/s,这是测试中AMD系统的两倍以上,几乎是Intel系统的三倍。关于ANANDTECH的这份测试报告,可以访问这个链接来了解 https://www.anandtech.com/print/15578/cloud-clash-amazon-graviton2-arm-against-intel-and-amd

AWS Graviton2处理器的性能

与一年前发布的第一代 AWS Graviton 处理器相比,Graviton2 处理器不管在性能还是功能上都实现了一次巨大的飞跃。它们都支持 Amazon EC2 M6g、C6g 和 R6g 实例,而且与当前这一代基于 x86 的实例相比,这些实例为各种工作负载(包括应用程序服务器、微服务、高性能计算、电子设计自动化、游戏、开源数据库和内存中的缓存)提供高达 40% 的性价比提升。AWS Graviton2 处理器也为视频编码工作负载提供增强的性能,为压缩工作负载提供硬件加速,并为基于 CPU 的机器学习推理提供支持。它们可以提供高 7 倍的性能、多 4 倍的计算核心、快 5 倍内存和大 2 倍缓存。

在AWS re:Invent 2019大会上,EC2产品团队分享的几组处理器Benchmark的结果就让人兴奋不已。


此外,在今年3月份KeyDB分享了它们针对M6g vs M5实例上的性能对比测试。众所周知,KeyDB是Redis的多线程超集,由于其先进的体系结构,具备了很好的性能表现。

他们的测试结论是这样的 –

“M5实例使用Intel Xeon Platinum 8175处理器,与其他大多数可用实例类型相比,它们通常为我们带来非常好的效果。令我们震惊的是,在较小的使用AWS Graviton2处理器的M6g实例上,与现有的KeyDB M5实例相比,获得了如此巨大的收益。

m6g.large比m5.large快1.65倍,而m6g.xlarge则比m5.xlarge快1.45倍。随着内核数量的增加,两种产品之间的差距开始缩小。但是,我们仍在研究m6g.2xlarge和m6g.4xlarge的性能,因为我们相信可以将性能水平提高到相同的倍数。在此测试中,我们没有针对M6g进行任何调整,因此我们对即将到来的结果感到乐观。”

而在今年的5月15日,在测试工具市场久负盛名的Phoronix 发表了一篇关于使用Graviton2 CPU的新M6g 实例的性能测试的文章。他们使用M6g实例作为基准。然后将这些实例与较早的A1Graviton实例进行比较。在Intel Xeon方面,选择了M5实例,在AMD EPYC方面选择的是M5a实例。该测试报告的全文的链接在这里,https://www.phoronix.com/scan.php?page=article&item=amazon-graviton2-benchmarks&num=1

这里仅仅撷取其中的部分结果以供各位参考:

  1. Linux Kernel(V5.4)的编译时间,数字越小越好
  2. Memcached mcperf v1.6.0, 数字越大越好

EC2 实例吞吐量的基础概念 – vCPU 与核心

除了实例类型之外(例如M5、C4),其它用于描述实例能力的最重要的指标就是其vCPU的数量。所谓的vCPU是Virtual Central Processing Units 的缩写。vCPU 本质上是指虚拟机上可用的逻辑CPU的内核。但EC2 的逻辑CPU计算方法却并非是基于物理上的CPU内核。准确的说,并发线程的数量即为EC2 vCPU的数量。例如,默认情况下,M5.xlarge 实例类型有两个 CPU 内核,每个内核支持两逻辑个线程,这样该类型实例的vCPU的数量即为4个。EC2实例范围通常从1个vCPU到最多128个,常见的实例的vCPU的数量多为2、4、8、16、32、48、64和96等。

Graviton 2是不带SMT的单路64核平台。所谓的SMT(Simultaneous multithreading) 的含义其实就是我们熟知的超线程技术。简单来说,SMT技术可以在一个实体CPU中提供两个逻辑线程,通过分享处理器的资源来提高性能。在Intel 的CPU中类似的技术被称作Hyper-Threading,或者简称HT。目前,Graviton 2 最大可用vCPU实例大小为就是64。

但是,这也意味着在谈论例如64个vCPU 实例的时候(在EC2中的规格称为16xlarge),对于Graviton2实例我们将获得64个物理核心,而对于AMD或Intel系统,我们将仅获得32个具有SMT的物理核心。这确实有一点“不公平”的味道,但是考虑到规格描述的一致性,这一点差别只好被忽略了。

ARM 实例的应用优化

Graviton 2目前被用于EC2 家族中的M6g、C6g以及R6g实例。其中,

  • M6g实例用于具有CPU、内存和网络资源平衡的通用工作负载
  • C6g 实例用于计算优化的工作负载,例如视频编码、建模和游戏服务器
  • R6g实例用于内存优化的工作负载,可处理内存中的大型数据集(如数据库)

与我们熟悉的型x86-64 架构不同,Graviton 2支持ARM V8.2和其它几个架构扩展。特别要强调的是,Graviton2支持 用于原子操作的LSE (Large System Extension) 指令集的扩展,可以提高大系统之间的锁定和同步性能。此外,它还支持FP16和用于机器学习的INT8等。毫不夸张的说,这一次Graviton2给我们带来的足够的惊喜。但不可否认的是,ARM处理器的体系结构与以往我们所熟悉的x86-64处理器的差异还是非常之大的。简而言之,如果我们不掌握针对ARM处理器的应用优化的方法,我们所看到的这一切性能上的提升不过是镜花水月。

C/C++ 代码在 Graviton 上的优化

C/C ++代码将极大的受益于设置优化代码的编译器标志并启用ARM特定的功能。

  • GCC/G++编译选项
    CPU GCC LLVM

    Graviton

     

    -march=armv8-a+crc+crypto -march=armv8-a+crc+crypto
    Graviton 2 -march=armv8.2-    a+fp16+rcpc+dotprod+crypto -march=armv8.2-a+fp16+rcpc+dotprod+crypto

    -march 编译项告诉编译器应该为系统的处理器架构生成什么代码,即向编译器声明应该为某种CPU架构生成代码。不同的CPU具有不同的功能,支持不同的指令集,执行代码的方式也不同。march标志将指示编译器为系统的CPU生成特定的代码,包括CPU的所有功能、特性、指令集、异常等等。
    ** 需要注意的一点是,对于GCC 7.x和8.1、8.2和8.3 等版本 -march=native不能正确地检测Graviton 2体系架构。务必请使用-march=armv8.2-a 而不是-march=native。GCC 9.x 以及10.x 则可以很好的识别Graviton 2的体系结构。目前Amazon Linux 2 缺省安装的GCC版本是7.3.1,而 Ubuntu 20.04 缺省安装的GCC 版本为9.3.0,还可以通过sudo apt install gcc-10 安装 GCC 10.0.1。

    CPU GCC < 9 GCC >= 9

    Graviton

     

    -mtune=cortex-a72 -mtune=cortex-a72
    Graviton 2 -mtune=cortex-a72 -mtune=neoverse-n1

    -mtune此选项指定GCC为其调整代码性能对应特定目标ARM处理器类型,可以通过使用这个选项来实现更好的性能。此外,此选项可以指定GCC为big.LITTLE系统调整代码的性能。
    所谓的big.LITTLE ,是ARM的异质运算多核心处理器技术。具体做法是将比较耗电、但运算能力强的处理器核心组成的“big集群”与低耗电、运算能力弱的处理器核心组成的“LITTLE集群”结合在一起,这些处理器核心共享存储器区段,并能够在不同的CPU集群之间在线实时分派、切换负载。

  • Large-System Extensions (LSE)
    Graviton 2处理器支持ARMv8.2指令集。LSE则提供低成本的原子操作。原因是是LSE提高了CPU对CPU的通信、锁和互斥的系统吞吐量。当使用LSE而不是加载/存储独占时,这种改进可以提升一个数量级。POSIX线程库需要LSE原子指令。LSE对于锁定和线程同步例程很重要。例如Amazon Linux 2 与Ubuntu 20.04均发布了一个支持LSE指令的libc 库。编译器需要为使用原子操作的应用程序生成LSE指令。例如,像PostgreSQL这样的数据库代码包含原子结构;带有std::原子语句的C++11代码转换为原子操作。GCC的-march=armv8.2-a标志支持所有由Graviton2支持的指令,包括了LSE。如果需要满足对于LSE的支持,还需要libc 的版本要高于2.3.0。目前Amazon Linux 2 的glibc 的版本为2.26, Ubuntu 20.04 的libc 的版本为2.31。

Java 程序在Graviton 上的优化

Java是一种通用编程语言。 编译后的Java代码可以在支持Java的所有平台上运行,而无需重新编译。 Java应用程序通常被编译为可在任何Java虚拟机(JVM)上运行的字节码,而与基础计算机体系结构无关。Java受到了包括ARM在内的广泛的支持,并且在ARM64上是开箱即用的。 Amazon Corretto是一种免费的,跨平台的,可立即投入生产的Open Java Development Kit(OpenJDK)发行版,支持由Graviton驱动的实例。如要获得Amazon Corretto 的安装包请访问这个链接https://aws.amazon.com/corretto

注意:下载时需要选择aarh64 的安装包。

此外,OpenJDK 也提供了对于arm64 平台的支持。在Graviton 处理器上,可以选择安装openjdk-8、openjdk-11、openjdk-13以及openjdk-14 等不同的JDK版本。

Java JAR可以包含特定于体系结构的共享库。一些Java库检查是否找到了这些共享库,是否使用JNI调用了本机库,而不是依赖于该函数的通用Java实现。尽管代码可以工作,但是如果没有JNI,性能可能会受到影响。

检查JAR是否包含此类共享库的一种快速方法是简单地将其解压缩,并检查是否有任何结果文件是共享库,以及是否缺少aarch64(arm64)共享库:
$ unzip foo.jar
$ find . -name "*.so" | xargs file

Python程序在Graviton 上的优化

Python 程序解释执行的特点需要我们使用的Python解释器可以很好适配于Graviton 处理器。对于Python解释器优化的关键是确保解释器使用了PGO和LTO等优化编译的选项。获得Python 解释器编译选项的方法很简单,

python3 -c “import sysconfig; print(sysconfig.get_config_var(‘CONFIG_ARGS’))”

Amazon Linux 2 Ubuntu 20.03
Python 3.7.6 Python 3.8.2
‘–build=aarch64-koji-linux-gnu’ ‘–host=aarch64-koji-linux-gnu’ ‘–program-prefix=’ ‘–disable-dependency-tracking’ ‘–prefix=/usr’ ‘–exec-prefix=/usr’ ‘–bindir=/usr/bin’ ‘–sbindir=/usr/sbin’ ‘–sysconfdir=/etc’ ‘–datadir=/usr/share’ ‘–includedir=/usr/include’ ‘–libdir=/usr/lib64’ ‘–libexecdir=/usr/libexec’ ‘–localstatedir=/var’ ‘–sharedstatedir=/var/lib’ ‘–mandir=/usr/share/man’ ‘–infodir=/usr/share/info’ ‘–enable-ipv6’ ‘–enable-shared’ ‘–with-computed-gotos=yes’ ‘–with-dbmliborder=gdbm:ndbm:bdb’ ‘–with-system-expat’ ‘–with-system-ffi’ ‘–enable-loadable-sqlite-extensions’ ‘–with-dtrace’ ‘–with-lto’ ‘–with-ssl-default-suites=openssl’ ‘–without-ensurepip’ ‘–enable-optimizations’ ‘build_alias=aarch64-koji-linux-gnu’ ‘host_alias=aarch64-koji-linux-gnu’ ‘CFLAGS=-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong –param=ssp-buffer-size=4 -grecord-gcc-switches   -D_GNU_SOURCE -fPIC -fwrapv  ‘ ‘LDFLAGS=-Wl,-z,relro  -g  ‘ ‘CPPFLAGS= ‘ ‘PKG_CONFIG_PATH=:/usr/lib64/pkgconfig:/usr/share/pkgconfig’ ‘–enable-shared’ ‘–prefix=/usr’ ‘–enable-ipv6’ ‘–enable-loadable-sqlite-extensions’ ‘–with-dbmliborder=bdb:gdbm’ ‘–with-computed-gotos’ ‘–without-ensurepip’ ‘–with-system-expat’ ‘–with-system-libmpdec’ ‘–with-dtrace’ ‘–with-system-ffi’ ‘CC=aarch64-linux-gnu-gcc’ ‘CFLAGS=-g   -fstack-protector-strong -Wformat -Werror=format-security ‘ ‘LDFLAGS=-Wl,-Bsymbolic-functions  -Wl,-z,relro -g -fwrapv -O2   ‘ ‘CPPFLAGS=-Wdate-time -D_FORTIFY_SOURCE=2’

可以看得出来,在不同的Linux 分发版本中,Python 解释器的版本以及优化方法存在一些差异。对比起来 Ubuntu 20.04 的Python解释器无论是Pystone 还是Pytest-benchmark 都要表现的更好一些。

NumPy 与SciPy

对于一些场景,Python 应用需要使用NumPy 以及SciPy。通常我们通过pip3 install numpy scipy 安装其二进制版本。一些场景下,使用BLIS对SciPy和NumPy工作负载进行基准测试可以确定额外的性能改进。

注:BLIS是一个可移植的软件框架,用于实例化高性能BLAS高性能稠密线性代数库。

  • 在Ubuntu上用BLIS安装NumPy和SciPy
    在Ubuntu上安装python3-numpy 与python3-scipy程序包将安装带有BLAS和LAPACK库的NumPy和SciPy。用BLIS和OpenBLAS在Ubuntu和Debian上安装SciPy和NumPy:
    sudo apt -y install python3-scipy python3-numpy libopenblas-dev libblis-dev
    sudo update-alternatives --set libblas.so.3-aarch64-linux-gnu \
    /usr/lib/aarch64-linux-gnu/blis-openmp/libblas.so.3
    在blas 与lapack 之间进行切换
    sudo update-alternatives --config libblas.so.3-aarch64-linux-gnusudo update-alternatives -config liblapack.so.3-aarch64-linux-gnu

PyPy

此外,2019年7月25日,PyPy 宣布了对于Aarch64 架构的支持。在基于Graviton处理器的A1 实例上进行的性能测试。从结果来看,PyPy 对于Python 程序性能的提升是非常显著的。

在一台基于Graviton 2 的m6g.2xlarge的实例上运行Pystone,PyPy 的性能是CPython (Python 3.8.2)的21倍!! 性能的提升是非常的惊人了。

Go程序在Graviton 上的优化

Go是一种静态类型的编译型程序语言。Go支持开箱即用的arm64,可以在所有常见的发行版中使用。Go最新的升级提高了性能,所以请确保使用最新版本的Go编译器和工具链。目前 Go的最新版本是1.14,而Amazon Linux 2 与Ubuntu 20.04 提供的安装包的版本均为1.13。在Ubuntu 环境下,安装Go 最新版本的一个简单的途径还可以考虑通过snap 进行安装。在snap 中提供的Go 的版本为1.14.6。

Go 1.16

Go 的下一个版本1.16预计将于2021年初发布。预计Go编译器将会通过以下列出的几项特性来提高ARM 架构下程序的性能。

  • ARMv8.1-A Atmoics指令,可显着提高Graviton 2上的互斥公平性和速度,以及带有v8.1和更新指令集的现代Arm内核。
  • 复制性能得到改善,尤其是当地址未对齐时。

关于系统优化的话题总是会有太多的内容值得探讨。随着新的基于ARM 的实例的普及,越来越多的开发者一定会遇到应用优化的问题。这篇博客权当是抛砖引玉之作,期待你们的经验与反馈。我的邮箱地址是 lianghon@amazon.com

 

本篇作者

费良宏

费良宏,AWS Principal Developer Advocate。在过去的20多年一直从事软件架构、程序开发以及技术推广等领域的工作。他经常在各类技术会议上发表演讲进行分享,他还是多个技术社区的热心参与者。他擅长Web领域应用、移动应用以及机器学习等的开发,也从事过多个大型软件项目的设计、开发与项目管理。目前他专注与云计算以及互联网等技术领域,致力于帮助中国的 开发者构建基于云计算的新一代的互联网应用。