R能在苹果硅片上发挥作用吗?

2020-11-11 23:05:21

在今年早些时候的2020年WWDC大会上,苹果公司宣布他们的笔记本电脑将从英特尔过渡到基于ARM的处理器。这篇博客是关于R什么时候能在这个平台上工作的前景,基于在一台运行A12Z的开发机器上的实验,A12Z是“苹果硅”处理器之一。

新的平台将包括Rosetta 2,这是一个动态翻译框架,它运行为64位英特尔Mac构建的二进制代码,使用的是即时、动态的二进制代码翻译。好消息是,R似乎可以很好地处理动态翻译,所以R用户即使使用当前版本也不需要担心。然而,有趣的问题是,R是否也会发挥作用。本机执行预计会更快,而且无论如何,转换可能最终都要完成。

执行摘要是:虽然目前还没有针对该平台发布Fortran编译器,但GNU Fortran的一个开发版本似乎已经运行良好。在使用数值Nas进行计算时,NaN有效载荷传播会产生一些令人惊讶的结果,但这些结果可以通过更改浮点单元的模式来克服,R-devel中已经这样做了。更多细节请看这篇文章。

R至少需要Fortran 90编译器才能生成。Fortran R中的大部分代码以及基本和推荐的软件包仍然是Fortran 77版本,因此它可以通过f2c转换为C,并由C编译器编译。然而,一些代码已经使用了Fortran 90的特性和后端移植,这需要付出很大的努力。

此外,R附带了略微修改的Reference LAPACK和BLAS版本,它们需要Fortran 90编译器才能构建。尽管苹果在MacOS的加速框架中提供了BLAS和LAPACK的优化版本,但在新平台上也能提供参考LAPACK/BLAS,这是非常可取的。

GCC的GFortran支持64位ARM:五月早先的一篇博文是关于在QEMU仿真器中的64位ARM(Aarch64)上运行的Linux上构建和测试R的。然而,苹果硅平台使用的是GFortran还不支持的不同的应用程序二进制接口(ABI)。

目前,似乎没有其他Fortran 90编译器,也没有免费的非商业性的。具体地说,LLVM的Fortran编译器(现在又称为Flang)还没有完成。

虽然GFortran目前还不支持Apple Silicon(GCC主干也没有发布),但GCC有一个私人开发分支,包括Ian Sandoe的GFortran,我们进行了试验。

像往常一样,从源头上打造GCC需要先打造平台的GMP、MPFR和MPC。GMP(版本6.2.0)需要从主干向后移植补丁(为Apple硅片、汇编宏配置脚本)。重新生成配置脚本和本地生成文件也需要构建libtool。GMP的配置脚本必须显式运行--build=aarch64-apple-darwin20.0.0,即使是在本地构建时也是如此。GCC的配置脚本使用--with-sysroot运行,指定一个目录到通过Xcode-SELECT-P安装的MacOSX.sdk。

R需要许多依赖项,这些依赖项可以在R安装和管理之后使用Apple提供的Apple/LLVM工具链进行本机构建(不需要Fortran编译器)。我们还编译了Subversion的本机构建,尽管原则上可以从tarball构建R。

R和推荐的包是使用APPLE/LLVMclang编译C语言(使用CFLAGS=-Wno-error=implicit-function-declaration)和Objective C,并使用GFortran的开发版本编译Fortran代码)构建的。

许多针对R和推荐的包的测试都失败了,原因与平台有关,但结果都是因为相同的原因:NaN负载的惊人传播,例如,nna*1就是NaN。

浮点数的R‘s NA使用带有特殊有效负荷值的NaN表示。源自不涉及NA的计算的NAN具有不同的(例如,零)有效负载,因此可以与NA区分开来。NAN经常在没有显式检查的情况下传递给R内部的计算,同样的情况也发生在程序包代码和外部数值代码中,它们对R的NA概念和表示方式一无所知。

IEEE 754浮点算术标准没有规定如何通过计算传播NaN有效载荷。涉及NAS和/或其他NAN的计算结果取决于CPU/浮点单元、编译器优化(编译器可以对计算重新排序)和算法(例如,在假定输入值是有限的情况下,很容易忽略不需要计算结果的输入值,但是没有实际检查它们是有限的)。

在R的在线帮助(?na,?nan)中可以找到更多信息,包括不应依赖na和NaN之间的差异的免责声明(引用最近R-devel的措辞):

涉及‘nan’的计算将返回‘nan’或‘na’:这两个中的哪一个是不确定的,可能依赖于R平台(因为编译器可能会对计算进行重新排序)。

使用‘na’的数值计算通常会产生‘na’:一个可能的例外是也涉及‘nan’的地方,在这种情况下,任何一种情况都可能产生结果(这可能取决于R平台)。然而,这并不能保证,未来的CPU和/或编译器可能会有不同的行为。

相对于R Nas,英特尔FPU工作得相对较好:与NAs的二进制操作会产生Nas,即使涉及NAs也是如此(基于实验)。但目前,英特尔计算机上的R通常被构建为使用SSE指令进行计算,这对R Nas不起作用:与Nas的二进制操作仅在另一个参数不是NaN时,或者当NA是第一个参数时,才会产生NA,因此NaN+NA是NaN,但NA+NaN是NA。由于带有SSE的64位英特尔最近很可能是R的主流设置,测试已经更新以接受这一行为。

然而,事实证明,在A12Z上,R的NA在任何二进制操作(有效负载丢失)之后变成了正常的NAN,所以即使是NA*1也是NAN。这在上面引用的R文档中警告过的范围内,但是仍然有一些R测试和推荐的包捕获了(记录为不可靠的)行为,期望像这样的操作将返回NA。我们还没有调查过有多少其他CRAN/BIOS包可以这样做。

ARM架构浮点单元(VFP、NEON)支持快速运行模式,包括清零和默认NAN。后者意味着NaN操作数的有效负载不会传播,所有结果NAN都具有默认有效负载,因此在R中,即使NA*1也是NAN。幸运的是,Runfast模式可以被禁用,当它被禁用时,NAN有效负载传播比使用Intel SSE更友好(NaN+NA是NA)。因此,我们在ARM启动时将R更新为DisableRunFast模式,从而解决了观察到的所有问题。

我们之前没有遇到这个问题,因为Runfast已经在其他平台上被默认禁用,包括Raspberry Pi(在带有32位ARM的旧型号2上测试,BCM2835),以及在模拟Aarch64(64位ARM,Cortex-A72)的QEMU上测试。

由伊恩·桑多(Ian Sandoe)为苹果硅提供支持的实验GCC在GCC的苹果硅开发分支机构工作。

浮点异常跟踪和NaN传播也是由Agner Fog编写的关于NaN有效负载传播的文本。