云服务器价格_云数据库_云主机【优惠】最新活动-搜集站云资讯

轻量服务器_vps和虚拟主机区别_好用

小七 141 0

编写更快的Jsonnet编译器

这篇博文是我们关于Databricks平台、基础设施管理、集成、工具、监控和供应的一系列内部工程博客的一部分。关于这里讨论的一些技术的背景,请参阅我们之前关于声明性基础设施的文章。在Databricks,我们使用Jsonnet数据模板语言来声明性地管理运行在Azure和AWS中的几十个区域的服务的配置。超过10万行Jsonnet定义了Databricks的基础设施、生成服务配置、Kubernetes部署、Cloudformation和Terraform模板等。我们已经达到了Jsonnet编译时间成为瓶颈的规模:即使在快速的多核机器上,完整的重新编译也要花费10分钟以上。为了解决这个问题,我们实现了Sjsonnet,这是一个新的Jsonnet编译器,在我们的实际使用中可以达到30-60倍的加速。什么是Jsonnet?我们之前已经写了为什么在这里使用Jsonnet,但简而言之:它允许您以一种方便、结构化的方式对JSON/YAML/etc.配置进行模板化:使用regex、mustache/jinja/.erb模板没有不安全的字符串模板它允许您在整个基础结构中重用常见的配置片段,避免复制粘贴配置,并使维护和确保一致性更容易它是可复制的:与使用像Python这样的通用语言来配置部署相比,Jsonnet的唯一输入是.Jsonnet文件,它唯一的输出是.json配置文件。你可以确定你的系统今天的配置方式和昨天一样,当你重构时,你可以简单地区分输出的JSON来验证你没有破坏任何东西Databricks有一个相对复杂的配置矩阵。许多云服务只有一个生产部署,通常是一个单一的单一应用程序,由所有客户共享。虽然需要注意开发/登台/生产之间的区别,但是通常一个相对简单的部署脚本就足够了。另一方面,Databricks支持多种部署方案:N不同的服务:Databricks有单独的服务来管理工作集群、服务于web应用程序、运行调度的作业和其他事情。多租户部署v.s.单租户部署v.s.私有云部署v.s.免费"社区"部署,例如https://community.cloud.databricks.com在Amazon Web Services v.s.Microsoft Azure上部署最后,开发、登台和生产环境这些因素中的每一个都是乘法的,导致了几十种不同的部署配置。Jsonnet重用通用配置和模板的能力,虽然完全具有确定性和可复制性,但它一直是我们理解和维护部署能力的核心,即使它们不断扩展以支持新的特性和产品Jsonnet性能问题随着时间的推移,我们注意到将.jsonnet文件编译为其输出JSON或YAML有时会花费大量时间:$time jsonnet kubernetes/config//配置jsonnet--输出文件输出.json...真3m24.375s用户3m20.962s系统0m1.329s一些最慢的.jsonnet文件,如上面所示的文件,最终需要一分钟以上的时间来评估。对于一个只传递一些字典的编译器来说,这是一个荒谬的时间量!在本例中,output.json文件只有大约500千字节:$杜克输出.json556千输出.json不是很小,但也不是要编译的大量配置。究竟是哪些文件需要时间似乎有些武断,也不清楚是什么原因造成的,但速度的放缓肯定会导致问题:自动化测试套件、生产部署以及开发更改的手动测试都放慢了速度。在Databricks,我们努力确保我们的开发人员尽可能的高效,而让一些工作流无缘无故地慢得离谱是没有好处的。因此,随着时间的推移,我们花了大量的时间来尝试如何提高Jsonnet编译性能。为什么Jsonnet慢?经过一番调查,我们发现导致Jsonnet编译缓慢的两大疑点:Jsonnet不会重复使用中间结果,并且每次都会重新评估它们Jsonnet的内置函数是用Jsonnet本身实现的,而不是用宿主语言实现的我们将逐一调查。Jsonnet不会重复使用中间结果Jsonnet提供了一个标准跟踪函数,用于在计算表达式时打印:$猫foo.jsonnet公司局部x=标准跟踪("评估",123);十$jsonnet公司foo.jsonnet公司跟踪:foo.jsonnet公司:1评估123第一个参数"evaluation"与调试信息(文件名、行号)一起打印,而第二个参数123成为标准跟踪函数调用。在这里,我们看到:"评估"打印一次123从返回标准跟踪123成为局部变量x的值123最终成为foo.jsonnet公司文件并打印到控制台。如果我们把局部变量链在一起,我们可以看到每个局部变量只计算一次:$猫foo.jsonnet公司局部x1=标准跟踪("评估",1);局部x2=x1+x1;x2个$jsonnet公司foo.jsonnet公司跟踪:foo.jsonnet公司:1评估2到目前为止,这一切都很好,如果您是使用Python、Javascript或任何其他常用语言编程的,那么您将看到这样的结果。但是,在其他一些情况下也会出现奇怪的行为:例如,如果我不是直接引用局部变量,而是引用对象的字段:$猫foo.jsonnet公司本地x1={"k":标准跟踪("评估",1)};局部x2={"k":x1.k+x1.k};x2.k.公司$jsonnet公司foo.jsonnet公司跟踪:foo.jsonnet公司:1评估跟踪:foo.jsonnet公司:1评估2这里我们可以看到当x2计算x1.k+x1.k时标准跟踪呼叫运行两次!结果发现字典字段的求值更像方法求值而不是字段引用:每次请求值时,都会重新计算字段。虽然Jsonnet的语义(再现性和纯度)确保重复评估的结果总是相同的,但它隐藏了潜在的性能问题。简单地说,这不是您所期望的,从另一种编程语言(如Python或Javascript)进入Jsonnet!现在我们知道了这一点,很明显,看起来无害的片段在评估它们所花费的时间内会有惊人的爆炸:$猫foo.jsonnet公司局部x0={k:1};局部x1={k:x0.k+x0.k};局部x2={k:x1.k+x1.k};局部x3={k:x2.k+x2.k};局部x4={k:x3.k+x3.k};局部x5={k:x4.k+x4.k};局部x6={k:x5.k+x5.k};本地x7={k:x6.k+x6.k};本地x8={k:x7.k+x7.k};本地x9={k:x8.k+x8.k};局部x10={k:x9.k+x9.k};局部x11={k:x10.k+x10.k};局部x12={k:x11.k+x11.k};局部x13={k:x12.k+x12.k};局部x14={k:x13.k+x13.k};局部x15={k:x14.k+x14.k};局部x16={k:x15.k+x15.k};局部x17={k:x16.k+x16.k};局部x18={k:x17.k+x17.k};局部x19={k:x18.k+x18.k};局部x20={k:x19.k+x19.k};局部x21={k:x20.k+x20.k};x21.k版$time jsonnet公司foo.jsonnet公司2097152实数0m5.064s用户0m5.031s系统0m0.015s这里,我省去了标准跟踪调用,因为在评估这个小片段时,它将被打印超过200万次。即便如此,从time命令中可以看到,计算这个代码段需要整整5秒的时间,这个数字将随着阶段数的增加而呈指数级上升。这种病态的情况在实际使用中并不存在:在试图配置cloudformation数据库或kubernetes集群时,没有人会编写一个级联的本地xN={k:xM.k+xM.k};绑定。另一方面,在实际使用中,最终使用的值和操作的类型往往比1+1复杂得多:您可能正在构造大型字符串、排序数组或md5散列输入。在这种情况下,完全可以用看起来无害的小Jsonnet片段来结束,这些片段生成的JSON输出很小,看起来很无害,但是编译这些输出需要几分钟的时间!Jsonnet的内置函数在Jsonnet本身中实现Jsonnet在std对象中提供了一个标准函数库。我们已经看到了标准跟踪,但还有其他一些常见的事情标准解析要将字符串转换为数字,标准基准64对字符串进行base64编码,或标准分类对值数组进行排序。所有这些都是组合配置时的常见任务。事实证明,Jsonnet的标准库在C++和GO实现中都主要在JSONET中实现。这是一个有点不寻常的决定:Jsonnet编译器是为定制配置而设计的:例如,允许您重用一个公共字符串,而不是在不同的YAML文件之间复制粘贴它,或者定义一个标准的部署模板,每个部署都可以用主机名/凭据/ip地址等填充。。这通常不是特别的计算密集型:只是传递一些字典并在这里和那里添加键。与实现base64编码器的计算非常不同:base64(输入)::本地字节=如果标准类型(input)="字符串",则标准地图(功能(c)标准码位(c) ,输入)其他的输入;本地辅助(arr,i,r)=如果我>=标准长度(arr)那么r否则,如果i+1>=标准长度(arr)那么局部str=//i的6 MSBbase64表[(arr[i]&252)>>2]+//2个最低有效位base64_table[(arr[i]&3)>2]+//最大有效位为2,最大有效位为4+1base64表[(arr[i]&3)4]+//i+1的4 LSBbase64_table[(arr[i+1]&15)2