由于无服务器计算的易编程性和易管理,近年来它得到了迅速的普及。许多人将其视为云服的下一个通用计算平台[4]。 但是,虽然现有的无服务器平台已经成功地支持了一些流行的应用,如事件处理和简单的ETL,但它们还是无法支持那些对延迟和吞吐量敏感的应用,如流式计算和机器学习(ML)。无服务器计算所面临的主要挑战源于这些应用(通常被部署在虚拟机上)对性能的要求与现有无服务器平台的性能之间的差距。
在这篇博文中,我们认为如果要缩小与传统的基于虚拟机的解决方案的性能差距,无服务器平台需要增加对以下特征的支持:
1.有状态计算
2.通信和数据感知函数的放置
这些都是10倍的特征,即这些特性能把一个应用的延迟和/或吞吐量提升10倍或更多。
为了验证这些声明,我们将这两个功能添加到了Ray(一个以前只提供无服务器类似抽象的通用系统)。在添加有状态计算和通信以及数据感知函数放置之后,Ray能够支持新的对吞吐量和延迟敏感的应用,而这在现有无服务器抽象中是不可能办到的。
评估性能的差距
无服务器平台允许开发人员通过在大型集群上透明地分发和执行这些函数来并行运行。通常这些函数都是无状态的,即每个函数从共享存储器(例如,S3)读取输入,执行计算,再将输出写回共享存储器,然后其他函数可以使用它的输出。
虽然无服务器计算让开发人员不必再操心如何管理集群,但它也带来了显着的性能上的开销[4]:
- 数据传输:今天,云计算函数需要从共享存储器(如S3)读取和写入数据。使用AWS Lambdas(号称是最成熟的无服务器产品),一个云函数(lambda)可以以70-80MB/s的速度从S3读取数据,并以大约50MB/s的速度向S3写入数据。访问S3的延迟可达数十毫秒。这个吞吐量和延迟都比访问本地内存甚至本地SSD差了几个数量级。这些不成比例的性能差异显著地影响了对延迟和吞吐量敏感的应用(例如机器学习)。
图1. 用AWS lambda从S3读/写的吞吐量。平均写入速度为约50MB/s,而读取吞吐量有两个峰值,一个约70MB/s和一个约85MB/s。(感谢Eric Jonas提供内容,http://ericjonas.com/。)
- 启动时间:云函数启动可能需要几秒钟,在某些情况下需要十多秒。启动时间包括(1)调度和启动云函数,以及(2)下载应用软件环境。虽然云函数可以在亚秒级[3]启动,但下载软件环境会需要更长的时间。例如,Python程序需要在启动之前下载数百MB的库和环境依赖项,这就可能用掉十几秒。
- 通信成本:与基于虚拟机的解决方案相比,云函数可能需要传输10倍或更多的数据。这是因为无服务器平台没办法为应用来优化云函数的放置位置。例如图2显示了聚合通信的模式,即通常由SQL聚合查询或分布式SGD生成的模式。对于虚拟机的方案和基于函数的方案,通过为每个虚拟机打包K个函数,基于虚拟机的解决方案比无服务器解决方案传输的数据少K倍,通常K在10~100之间。
图2:(a)一个云函数聚合来自N个其他函数的结果。(b)相同的聚合模式,其中N个函数在N / K个虚拟机实例上运行,每个实例运行K个函数。基于虚拟机的解决方案传输的数据量是1/K。更重要的是,聚合结果的函数接收的数据也更少
有没有解决的方法?
最近,已经出现了几个改善无服务器平台和云函数服务性能[2,4]的建议。 这些建议包括:
1.更快的共享存储系统(例如,基于内存或NVRAM支持的系统)。
2.通过保持云函数处于内存状态来减少功能启动的时间。
3.函数之间直接通信。
虽然这些建议可以显著地提高现有云函数的性能,但我们认为它们并不足以支持新的工作负载,例如机器学习的训练、模型服务和流式计算。
首先,正如我们接下来将看到的,即使共享存储系统在内存中,远程访问该存储数据仍然比从本地存储器访问相同数据要慢得多,甚至比从以下位置访问数据要慢得多——硬件加速器(如GPU)的芯片内存。其次,虽然保持函数处于内存里肯定有帮助,但启动时间仍然是几百毫秒[7]。 最后,虽然直接通信消除了对共享存储器的读/写需求,但它不控制函数所处的位置,因此不能解决如图2所示的低效通信模式的问题。
为了进一步说明这些改进建议的不足,接下来会介绍我们在加州大学伯克利分校建立的通用分布式系统Ray的经验。
Ray的任务——无服务器平台的“最佳”性能
Ray最初是一个面向任务的框架,这里的任务是指的远程运行的无状态函数,类似于无服务器平台。由于多租户不是Ray的原始目标,因此我们是可以超越上述的性能改进建议的。我们认为Ray的性能是当前无服务器架构可实现的性能的实际上限。Ray采用了大量优化措施,可以显著地缩短启动时间,并提高函数之间的数据传输的吞吐量。
- 基于内存的共享存储:为了提高读取函数输入和输出的吞吐量和延迟,我们实现了基于内存的存储引擎。同一台机器上的函数在共享数据时使用内存共享,从而避免了数据的复制。这使得函数能够以几GB/s的速率从/向共享存储读取/写入数据,这比访问诸如S3这样的存储器(参见图1)的速度高出几个数量级。
- 毫秒级启动时间:在Ray里,当声明函数时,函数及其环境的代码会被急迫地分发到集群的所有节点,而不是在调用函数时才被分发。 因此,函数的启动时间被极大地缩短了。在同一台机器上执行no-op函数需要大约300微秒,在远程机器上需要大约1 毫秒(这些结果是在AWS中运行的集群上获得的)。这比在现有平台上运行云函数所需的时间(可能是100毫秒或更长[7])快了几个数量级。
虽然这些设计决策和实现方法为现有云函数提供了显著的性能改进,它们比近期提出并在上一节中描述的技术的改进更大,但我们发现它们不足以支持一些应用,例如机器学习和流式计算。
无状态函数还不够好
最初Ray面向的主要工作负载之一是分布式机器学习和强化学习应用。这些工作负载很快就暴露了Ray的功能抽象的局限性,其中许多限制也在其他应用中出现,例如流式计算和数据库[2]。
低效的GPU训练
在许多情况下,我们希望在相同的数据上训练相同的模型,但是从不同的初始状态开始。 使用一个函数抽象,模型权重和训练数据需要在每轮训练中被复制到GPU。不幸的是,即使数据已经在本地内存中(而不是在磁盘上或远程存储上),将其传输到GPU也需要花费大量的时间。这是因为GPU通常只能通过PCI Express总线从内存读取数据,而目前PCI Express总线的最大速率为32GB/s。这与今天的GPU中可用的内存带宽(从500 GB/s开始)相差甚远。 使用无状态函数抽象,我们无法在GPU内存中保持训练轮次之间的状态,从而最大化性能。
图3. 模拟器的例子。(a)每个动作都用一个无状态函数实现,从共享存储中读取模拟器的状态,执行一个模拟步骤,并写回更新的状态。(b)由有状态的运算实现的相同场景。模拟器的状态是内部的,因此无需传输状态数据。(c)不暴露状态的封闭源模拟器。在这种情况下,我们别无选择,只能将模拟器实现为有状态运算
序列化和反序列化的开销
在Ray中,任务输入/输出的读/写性能被限制在8GB/s左右,比直接访问内存低10倍。尽管数据存储在同一台机器上的共享内存中,但我们使用Apache Arrow对数据格式进行快速序列化和反序列化。图3说明了运行模拟器的情况,这是强化学习应用的一个常见工作负载。一个自然的解决方案是为每个模拟步骤运行一个任务,如图3(a)所示。在每个步骤中,任务将(1)使用当前状态初始化模拟器;(2)基于某些策略应用某个动作,以及(3)读取模拟器的状态(将在下一步使用),它可能是奖励。但是,这需要从外部存储器读取/写入模拟器的状态,并承担大量的序列化/反序列化的开销。假设模拟器状态为80MB,外部存储器的读/写吞吐量为8MB/s,更新状态需要1毫秒。这意味着读取状态需要10毫秒,写回来需要10毫秒,比更新状态慢20倍!相反,如果我们使用一个在本地存储状态的有状态运算,则只需1毫秒就可执行一步模拟(参见图3(b))。
缺乏对闭源模拟器的支持
如果模拟器是闭源的(例如星际争霸2),我们甚至无法访问模拟器的完整状态。 结果,我们被迫将模拟器视为黑盒子。在每一步,我们都做一个动作,不是读取模拟器的状态,而是获取模拟器内部状态的外部观察。典型的观察是每次动作后屏幕的图像。不幸的是,由于我们无法使用当前状态初始化模拟器,因此我们无法再将任务抽象用于模拟。在这种情况下,将模拟器包装成有状态运算是唯一可行的解决方案。图3(c)说明了这种情况。
Ray的教训:在无服务器中找到缺失的链接
有状态运算
如上所述,虽然无状态计算是优雅且易于推理的,但即使数据和计算在同一台机器上,它们也会产生很大的开销(例如,数据存储在本地内存中而计算运行在GPU)。为了支持更广泛的应用,除了将数据和计算共同放置并添加对有状态计算(例如行动者)的支持外,我们看不到其他选择。行动者封装了可变状态,这使它们能够避免在同一个行动者上的连续操作之间进行昂贵的状态传输。
在Ray中,行动者允许我们执行有效的训练、交互式查询处理以及支持专有模拟器。例如,在训练时,网络权重和训练数据被封装为行动者的状态。然后,一个轮次的训练就变成为在该行动者上的方法调用。因此,新的训练轮次只需要重新初始化模型权重,这比读/写和序列化/反序列化这些权重要经济得多。通过行动者和任务抽象,Ray支持了更广泛的应用,其性能优于当前的无服务器平台。
放置位置控制
如上所述,当需要在云函数间共享大量数据时,缺少放置位置的控制可能导致性能降低几个数量级。解决此问题有两种通用的方法:
- 底层API。在这种方法中,无服务器平台可以提供灵活的底层机制,使开发人员能够在应用级别实现所需的策略。这是Ray采用的方法,它为应用提供了定义逻辑资源并将函数与这些资源相关联的能力[5]。例如,这允许应用通过指定相同的资源调度在同一节点上运行两个函数。这使我们能够实现能相当于基于虚拟机的解决方案(例如,图2的(b))的高效的通信模式、高性能SGD和复杂应用(例如AlphaGo)。
- 声明式API。在这种方法中,无服务器平台可以公开一个API,让应用指定他们的首选项,例如TetriSched在[6]里提出的“n Choose k”模式。在此模式中,用户可以指定作业可以使用n个等效资源中的任意k个。
结论和尚未解决的挑战
总之,本博客认为,要实现提供通用计算平台以支持各种应用的承诺,无服务器计算平台需要有(1)有状态计算和(2)控制函数放置位置以最小化数据传输的能力。其中每一个都可以将数据传输的性能提高至少10倍,和/或将传输的数据量减少10倍或更多。
作为证明,在Ray中,我们通过添加对这两个特性的支持来扩展原始任务(函数)抽象。 这些扩展使Ray能够支持对延迟和吞吐量敏感的应用,包括流式计算、分布式训练和模拟。而这些应用要单独使用函数是不可能实现的。
展望未来,重新构建现有的无服务器平台以支持长时运行的有状态计算和放置位置控制将是令人兴奋的。在这种情况下,我们注意到在多租户环境中Ray需要提供的两个有挑战的优化:单节点共享内存和在启动函数之前主动推送函数代码。将函数推送到整个数据中心可能运行该函数的每个节点可能是不可行的。在保持Ray提供的性能改进的同时应对这些挑战是一个有趣的未来研究方向。
引用文献
[1]https://dl.acm.org/citation.cfm?id=3154630.3154660
[2]https://arxiv.org/abs/1812.03651
[3]https://www.usenix.org/system/files/conference/hotcloud16/hotcloud16_hendrickson.pdf
[4]https://arxiv.org/abs/1902.03383
[5]https://rise.cs.berkeley.edu/blog/ray-scheduling/
[6]https://dl.acm.org/citation.cfm?id=2901355
[7]https://www.usenix.org/conference/atc18/presentation/wang-liang